2a508d97c2461a2b6cda713e47a4f3f921122c69
[dcpomatic.git] / test / kdm_cli_test.cc
1 /*
2     Copyright (C) 2022 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #include "lib/cinema.h"
23 #include "lib/config.h"
24 #include "lib/content_factory.h"
25 #include "lib/cross.h"
26 #include "lib/dkdm_wrapper.h"
27 #include "lib/film.h"
28 #include "lib/kdm_cli.h"
29 #include "lib/screen.h"
30 #include "lib/trusted_device.h"
31 #include "test.h"
32 #include <boost/algorithm/string/predicate.hpp>
33 #include <boost/filesystem.hpp>
34 #include <boost/test/unit_test.hpp>
35 #include <iostream>
36
37
38 using std::dynamic_pointer_cast;
39 using std::string;
40 using std::vector;
41 using boost::optional;
42
43
44 optional<string>
45 run(vector<string> const& args, vector<string>& output, bool dump_errors = true)
46 {
47         std::vector<char*> argv(args.size());
48         for (auto i = 0U; i < args.size(); ++i) {
49                 argv[i] = const_cast<char*>(args[i].c_str());
50         }
51
52         auto error = kdm_cli(args.size(), argv.data(), [&output](string s) { output.push_back(s); });
53         if (error && dump_errors) {
54                 std::cout << *error << "\n";
55         }
56
57         return error;
58 }
59
60
61 BOOST_AUTO_TEST_CASE (kdm_cli_test_certificate)
62 {
63         vector<string> args = {
64                 "kdm_cli",
65                 "--verbose",
66                 "--valid-from", "now",
67                 "--valid-duration", "2 weeks",
68                 "--projector-certificate", "test/data/cert.pem",
69                 "-S", "my great screen",
70                 "-o", "build/test",
71                 "test/data/dkdm.xml"
72         };
73
74         boost::filesystem::path const kdm_filename = "build/test/KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV__my_great_screen.xml";
75         boost::system::error_code ec;
76         boost::filesystem::remove(kdm_filename, ec);
77
78         vector<string> output;
79         auto error = run(args, output);
80         BOOST_CHECK (!error);
81
82         BOOST_CHECK(boost::filesystem::exists(kdm_filename));
83 }
84
85
86 BOOST_AUTO_TEST_CASE(kdm_cli_specify_decryption_key_test)
87 {
88         using boost::filesystem::path;
89
90         ConfigRestorer cr;
91
92         path const dir = "build/test/kdm_cli_specify_decryption_key_test";
93
94         boost::system::error_code ec;
95         boost::filesystem::remove_all(dir, ec);
96         boost::filesystem::create_directories(dir);
97
98         dcp::CertificateChain chain(openssl_path(), 365);
99         dcp::write_string_to_file(chain.leaf().certificate(true), dir / "cert.pem");
100         dcp::write_string_to_file(*chain.key(), dir / "key.pem");
101
102         vector<string> make_args = {
103                 "kdm_cli",
104                 "--valid-from", "now",
105                 "--valid-duration", "2 weeks",
106                 "--projector-certificate", path(dir / "cert.pem").string(),
107                 "-S", "base",
108                 "-o", dir.string(),
109                 "test/data/dkdm.xml"
110         };
111
112         vector<string> output;
113         auto error = run(make_args, output);
114         BOOST_CHECK(!error);
115
116         vector<string> bad_args = {
117                 "kdm_cli",
118                 "--valid-from", "now",
119                 "--valid-duration", "2 weeks",
120                 "--projector-certificate", path(dir / "cert.pem").string(),
121                 "-S", "bad",
122                 "-o", dir.string(),
123                 path(dir / "KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV__base.xml").string()
124         };
125
126         /* This should fail because we're using the wrong decryption certificate */
127         output.clear();
128         error = run(bad_args, output, false);
129         BOOST_REQUIRE(error);
130         BOOST_CHECK_MESSAGE(error->find("Could not decrypt KDM") != string::npos, "Error was " << *error);
131
132         vector<string> good_args = {
133                 "kdm_cli",
134                 "--valid-from", "now",
135                 "--valid-duration", "2 weeks",
136                 "--projector-certificate", path(dir / "cert.pem").string(),
137                 "--decryption-key", path(dir / "key.pem").string(),
138                 "-S", "good",
139                 "-o", dir.string(),
140                 path(dir / "KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV__base.xml").string()
141         };
142
143         /* This should succeed */
144         output.clear();
145         error = run(good_args, output);
146         BOOST_CHECK(!error);
147 }
148
149
150 static
151 void
152 setup_test_config()
153 {
154         auto config = Config::instance();
155         auto const cert = dcp::Certificate(dcp::file_to_string("test/data/cert.pem"));
156
157         auto cinema_a = std::make_shared<Cinema>("Dean's Screens", vector<string>(), "");
158         cinema_a->add_screen(std::make_shared<dcpomatic::Screen>("Screen 1", "", cert, boost::none, std::vector<TrustedDevice>()));
159         cinema_a->add_screen(std::make_shared<dcpomatic::Screen>("Screen 2", "", cert, boost::none, std::vector<TrustedDevice>()));
160         cinema_a->add_screen(std::make_shared<dcpomatic::Screen>("Screen 3", "", cert, boost::none, std::vector<TrustedDevice>()));
161         config->add_cinema(cinema_a);
162
163         auto cinema_b = std::make_shared<Cinema>("Floyd's Celluloid", vector<string>(), "");
164         cinema_b->add_screen(std::make_shared<dcpomatic::Screen>("Foo", "", cert, boost::none, std::vector<TrustedDevice>()));
165         cinema_b->add_screen(std::make_shared<dcpomatic::Screen>("Bar", "", cert, boost::none, std::vector<TrustedDevice>()));
166         config->add_cinema(cinema_b);
167 }
168
169
170 BOOST_AUTO_TEST_CASE(kdm_cli_select_cinema)
171 {
172         ConfigRestorer cr;
173
174         setup_test_config();
175
176         vector<boost::filesystem::path> kdm_filenames = {
177                 "build/test/KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV_Floyds_Celluloid_Foo.xml",
178                 "build/test/KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV_Floyds_Celluloid_Bar.xml"
179         };
180
181         for (auto path: kdm_filenames) {
182                 boost::system::error_code ec;
183                 boost::filesystem::remove(path, ec);
184         }
185
186         vector<string> args = {
187                 "kdm_cli",
188                 "--verbose",
189                 "--valid-from", "now",
190                 "--valid-duration", "2 weeks",
191                 "-c", "Floyd's Celluloid",
192                 "-o", "build/test",
193                 "test/data/dkdm.xml"
194         };
195
196         vector<string> output;
197         auto error = run(args, output);
198         BOOST_CHECK(!error);
199
200         BOOST_REQUIRE_EQUAL(output.size(), 2U);
201         BOOST_CHECK(boost::algorithm::starts_with(output[0], "Making KDMs valid from"));
202         BOOST_CHECK_EQUAL(output[1], "Wrote 2 KDM files to build/test");
203
204         for (auto path: kdm_filenames) {
205                 BOOST_CHECK(boost::filesystem::exists(path));
206         }
207 }
208
209
210 BOOST_AUTO_TEST_CASE(kdm_cli_select_screen)
211 {
212         ConfigRestorer cr;
213
214         setup_test_config();
215
216         boost::filesystem::path kdm_filename = "build/test/KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV_Deans_Screens_Screen_2.xml";
217
218         boost::system::error_code ec;
219         boost::filesystem::remove(kdm_filename, ec);
220
221         vector<string> args = {
222                 "kdm_cli",
223                 "--verbose",
224                 "--valid-from", "now",
225                 "--valid-duration", "2 weeks",
226                 "-c", "Dean's Screens",
227                 "-S", "Screen 2",
228                 "-o", "build/test",
229                 "test/data/dkdm.xml"
230         };
231
232         vector<string> output;
233         auto error = run(args, output);
234         BOOST_CHECK(!error);
235
236         BOOST_REQUIRE_EQUAL(output.size(), 2U);
237         BOOST_CHECK(boost::algorithm::starts_with(output[0], "Making KDMs valid from"));
238         BOOST_CHECK_EQUAL(output[1], "Wrote 1 KDM files to build/test");
239
240         BOOST_CHECK(boost::filesystem::exists(kdm_filename));
241 }
242
243
244 BOOST_AUTO_TEST_CASE(kdm_cli_specify_cinemas_file)
245 {
246         ConfigRestorer cr;
247
248         setup_test_config();
249
250         vector<string> args = {
251                 "kdm_cli",
252                 "--cinemas-file",
253                 "test/data/cinemas.xml",
254                 "list-cinemas"
255         };
256
257         vector<string> output;
258         auto const error = run(args, output);
259         BOOST_CHECK(!error);
260
261         BOOST_REQUIRE_EQUAL(output.size(), 3U);
262         BOOST_CHECK_EQUAL(output[0], "stinking dump ()");
263         BOOST_CHECK_EQUAL(output[1], "classy joint ()");
264         BOOST_CHECK_EQUAL(output[2], "Great ()");
265 }
266
267
268 BOOST_AUTO_TEST_CASE(kdm_cli_specify_cert)
269 {
270         boost::filesystem::path kdm_filename = "build/test/KDM_KDMCLI__.xml";
271
272         boost::system::error_code ec;
273         boost::filesystem::remove(kdm_filename, ec);
274
275         auto film = new_test_film2("kdm_cli_specify_cert", content_factory("test/data/flat_red.png"));
276         film->set_encrypted(true);
277         film->set_name("KDMCLI");
278         film->set_use_isdcf_name(false);
279         make_and_verify_dcp(film);
280
281         vector<string> args = {
282                 "kdm_cli",
283                 "--valid-from", "2024-01-01 10:10:10",
284                 "--valid-duration", "2 weeks",
285                 "-C", "test/data/cert.pem",
286                 "-o", "build/test",
287                 "create",
288                 "build/test/kdm_cli_specify_cert"
289         };
290
291         vector<string> output;
292         auto error = run(args, output);
293         BOOST_CHECK(!error);
294
295         BOOST_CHECK(output.empty());
296         BOOST_CHECK(boost::filesystem::exists(kdm_filename));
297 }
298
299
300 BOOST_AUTO_TEST_CASE(kdm_cli_time)
301 {
302         ConfigRestorer cr;
303
304         setup_test_config();
305
306         boost::filesystem::path kdm_filename = "build/test/KDM_Test_FTR-1_F-133_XX-XX_MOS_2K_20220109_SMPTE_OV_Deans_Screens_Screen_2.xml";
307
308         boost::system::error_code ec;
309         boost::filesystem::remove(kdm_filename, ec);
310
311         dcp::LocalTime now;
312         now.add_days(2);
313
314         vector<string> args = {
315                 "kdm_cli",
316                 "--verbose",
317                 "--valid-from", now.as_string(),
318                 "--valid-duration", "2 weeks",
319                 "-c", "Dean's Screens",
320                 "-S", "Screen 2",
321                 "-o", "build/test",
322                 "test/data/dkdm.xml"
323         };
324
325         vector<string> output;
326         auto error = run(args, output);
327         BOOST_CHECK(!error);
328
329         BOOST_REQUIRE_EQUAL(output.size(), 2U);
330         BOOST_CHECK(boost::algorithm::starts_with(output[0], "Making KDMs valid from"));
331         BOOST_CHECK_EQUAL(output[1], "Wrote 1 KDM files to build/test");
332
333         BOOST_CHECK(boost::filesystem::exists(kdm_filename));
334 }
335
336
337 BOOST_AUTO_TEST_CASE(kdm_cli_add_dkdm)
338 {
339         ConfigRestorer cr;
340
341         setup_test_config();
342
343         BOOST_CHECK_EQUAL(Config::instance()->dkdms()->children().size(), 0U);
344
345         vector<string> args = {
346                 "kdm_cli",
347                 "add-dkdm",
348                 "test/data/dkdm.xml"
349         };
350
351         vector<string> output;
352         auto error = run(args, output);
353         BOOST_CHECK(!error);
354
355         auto dkdms = Config::instance()->dkdms()->children();
356         BOOST_CHECK_EQUAL(dkdms.size(), 1U);
357         auto dkdm = dynamic_pointer_cast<DKDM>(dkdms.front());
358         BOOST_CHECK(dkdm);
359         BOOST_CHECK_EQUAL(dkdm->dkdm().as_xml(), dcp::file_to_string("test/data/dkdm.xml"));
360 }
361