Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package openQA for openSUSE:Factory checked 
in at 2026-03-27 16:50:10
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openQA (Old)
 and      /work/SRC/openSUSE:Factory/.openQA.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "openQA"

Fri Mar 27 16:50:10 2026 rev:829 rq:1343060 version:5.1774510397.efec10a7

Changes:
--------
--- /work/SRC/openSUSE:Factory/openQA/openQA.changes    2026-03-27 
06:45:30.436072607 +0100
+++ /work/SRC/openSUSE:Factory/.openQA.new.8177/openQA.changes  2026-03-27 
16:52:17.826440000 +0100
@@ -2 +2 @@
-Wed Mar 25 23:07:42 UTC 2026 - [email protected]
+Thu Mar 26 08:07:45 UTC 2026 - [email protected]
@@ -4 +4 @@
-- Update to version 5.1774473623.838c74ef:
+- Update to version 5.1774510397.efec10a7:
@@ -5,0 +6,3 @@
+  * docs(userguide): fix position of job_templates unique id
+  * docs(amqp): cover usage examples of amqp and slack integration
+  * feat: Allow resuming asset downloads with `openqa-clone-job`

Old:
----
  openQA-5.1774473623.838c74ef.obscpio

New:
----
  openQA-5.1774510397.efec10a7.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ openQA-client-test.spec ++++++
--- /var/tmp/diff_new_pack.rmEv1r/_old  2026-03-27 16:52:18.982488337 +0100
+++ /var/tmp/diff_new_pack.rmEv1r/_new  2026-03-27 16:52:18.982488337 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-client
 Name:           %{short_name}-test
-Version:        5.1774473623.838c74ef
+Version:        5.1774510397.efec10a7
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-devel-test.spec ++++++
--- /var/tmp/diff_new_pack.rmEv1r/_old  2026-03-27 16:52:19.014489675 +0100
+++ /var/tmp/diff_new_pack.rmEv1r/_new  2026-03-27 16:52:19.018489842 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-devel
 Name:           %{short_name}-test
-Version:        5.1774473623.838c74ef
+Version:        5.1774510397.efec10a7
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-test.spec ++++++
--- /var/tmp/diff_new_pack.rmEv1r/_old  2026-03-27 16:52:19.046491013 +0100
+++ /var/tmp/diff_new_pack.rmEv1r/_new  2026-03-27 16:52:19.046491013 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA
 Name:           %{short_name}-test
-Version:        5.1774473623.838c74ef
+Version:        5.1774510397.efec10a7
 Release:        0
 Summary:        Test package for openQA
 License:        GPL-2.0-or-later

++++++ openQA-worker-test.spec ++++++
--- /var/tmp/diff_new_pack.rmEv1r/_old  2026-03-27 16:52:19.074492183 +0100
+++ /var/tmp/diff_new_pack.rmEv1r/_new  2026-03-27 16:52:19.074492183 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-worker
 Name:           %{short_name}-test
-Version:        5.1774473623.838c74ef
+Version:        5.1774510397.efec10a7
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA.spec ++++++
--- /var/tmp/diff_new_pack.rmEv1r/_old  2026-03-27 16:52:19.110493689 +0100
+++ /var/tmp/diff_new_pack.rmEv1r/_new  2026-03-27 16:52:19.110493689 +0100
@@ -99,7 +99,7 @@
 %define devel_requires %devel_no_selenium_requires chromedriver
 
 Name:           openQA
-Version:        5.1774473623.838c74ef
+Version:        5.1774510397.efec10a7
 Release:        0
 Summary:        The openQA web-frontend, scheduler and tools
 License:        GPL-2.0-or-later

++++++ openQA-5.1774473623.838c74ef.obscpio -> 
openQA-5.1774510397.efec10a7.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1774473623.838c74ef/docs/Installing.asciidoc 
new/openQA-5.1774510397.efec10a7/docs/Installing.asciidoc
--- old/openQA-5.1774473623.838c74ef/docs/Installing.asciidoc   2026-03-25 
22:20:23.000000000 +0100
+++ new/openQA-5.1774510397.efec10a7/docs/Installing.asciidoc   2026-03-26 
08:33:17.000000000 +0100
@@ -1074,8 +1074,9 @@
 The messages consist of a topic and a body.
 The body contains json encoded info about the event.
 See 
https://github.com/openSUSE/suse_msg/blob/master/amqp_infra.md[amqp_infra.md]
-for more info about the server and the message topic format.
-There you will find instructions how to configure the AMQP server as well.
+for more info about the server and the message topic format or take a look at 
+<<UsersGuide.asciidoc#amqp_events,Consuming AMQP events from openQA>> to find
+detailed instructions how to configure the AMQP server.
 
 To let openQA send messages to an AMQP message bus,
 first make sure that the `perl-Mojo-RabbitMQ-Client` RPM is installed.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1774473623.838c74ef/docs/UsersGuide.asciidoc 
new/openQA-5.1774510397.efec10a7/docs/UsersGuide.asciidoc
--- old/openQA-5.1774473623.838c74ef/docs/UsersGuide.asciidoc   2026-03-25 
22:20:23.000000000 +0100
+++ new/openQA-5.1774510397.efec10a7/docs/UsersGuide.asciidoc   2026-03-26 
08:33:17.000000000 +0100
@@ -13,9 +13,8 @@
 <<Installing.asciidoc#installing,Installation Guide>> first to understand the 
structure
 of components as well as the configuration of an installed instance.
 
-
-== Using job templates to automate jobs creation
 [id="job_templates"]
+== Using job templates to automate jobs creation
 
 === The problem
 
@@ -1698,6 +1697,58 @@
 systemctl mask openqa-enqueue-asset-cleanup.timer
 ----
 
+[id="amqp_events"]
+== Consuming AMQP events from openQA
+The message topic follows the format `SCOPE.APPLICATION.OBJECT.ACTION`, for
+example `opensuse.openqa.job.done` or `opensuse.openqa.comment.create`.
+The `topic_prefix` setting controls the `SCOPE` part; if left empty the topic
+starts with `openqa.` directly.
+Consumers can use `#` to match any number of words or `*` to match exactly one
+word. `opensuse.openqa.#` subscribes to all openQA events, for instance.
+
+Here are the events triggered and published by openQA:
+
+1. `openqa.job.create` when a job is created
+2. `openqa.job.delete` when a job is deleted
+3. `openqa.job.cancel` when a job is cancelled
+4. `openqa.job.restart` when a job is restarted or duplicated
+5. `openqa.job.update_result` when a job result is updated
+6. `openqa.job.done` when a job finishes
+
+Job event bodies include the job settings (e.g. `BUILD`, `TEST`, `ARCH`,
+`MACHINE`, `FLAVOR`, asset fields like `ISO` or `HDD_1`) plus `id`, `group_id`,
+and `remaining` (number of pending jobs for the same build). Finished jobs
+additionally include `result`, `reason`, `newbuild`, `failedmodules`, `bugref`,
+and `bugurl` (only present when a bug reference exists).
+
+Example of a `*.openqa.job.done` message body (at the time of writing):
+
+[source,json]
+--------------------------------------------------------------------------------
+{"ARCH":"x86_64","BUILD":"N.487.1","FLAVOR":"Staging-DVD","ISO":"openSUSE-Staging:N-Tumbleweed-DVD-x86_64-Build487.1-Media.iso","MACHINE":"64bit","TEST":"autoyast_mini","bugref":null,"failedmodules":[],"group_id":2,"id":5735489,"newbuild":null,"reason":null,"remaining":13,"result":"passed"}
+--------------------------------------------------------------------------------
+
+Event for comments are also published:
+
+1. `openqa.comment.create`
+2. `openqa.comment.update`
+3. `openqa.comment.delete`
+
+Comment event bodies include: `id`, `job_id`, `group_id`, `parent_group_id`,
+`user`, `text`, `created`, `updated`.
+
+Example `*.openqa.comment.create` message body:
+
+[source,json]
+--------------------------------------------------------------------------------
+{"created":"2026-03-11T14:35:23Z","group_id":null,"id":865726,"job_id":5735489,"parent_group_id":null,"text":"amqp
 sent","updated":"2026-03-11T14:35:23Z","user":"demo"}
+--------------------------------------------------------------------------------
+
+A real-world example of an AMQP consumer built on top of openQA events is
+https://github.com/dirkmueller/slacky[slacky], a bot that monitors CI/build
+pipelines and posts failure notifications to Slack. It subscribes to openQA job
+events and tracks job results per build and job group.
+
 == CLI interface
 Beside the `daemon` argument to run the actual web service the openQA
 startup script `/usr/share/openqa/script/openqa` supports further arguments.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1774473623.838c74ef/lib/OpenQA/Script/CloneJob.pm 
new/openQA-5.1774510397.efec10a7/lib/OpenQA/Script/CloneJob.pm
--- old/openQA-5.1774473623.838c74ef/lib/OpenQA/Script/CloneJob.pm      
2026-03-25 22:20:23.000000000 +0100
+++ new/openQA-5.1774510397.efec10a7/lib/OpenQA/Script/CloneJob.pm      
2026-03-26 08:33:17.000000000 +0100
@@ -8,7 +8,6 @@
 use Cpanel::JSON::XS;
 use Data::Dump 'pp';
 use Exporter 'import';
-use LWP::UserAgent;
 use OpenQA::Client;
 use OpenQA::Jobs::Constants;
 use Mojo::File 'path';
@@ -36,6 +35,8 @@
     _GROUP_ID => '_GROUP',
 };
 
+use constant CURL => $ENV{OPENQA_CLI_CURL_PATH} // 'curl';
+
 my $TEST_NAME = TEST_NAME_ALLOWED_CHARS;
 my $TEST_NAME_PLUS_MINUS = TEST_NAME_ALLOWED_CHARS_PLUS_MINUS;
 my $SETTINGS_REGEX = 
qr|([A-Z0-9_]+(\[\])?)(:([$TEST_NAME]+(?:[$TEST_NAME_PLUS_MINUS]+[$TEST_NAME])?))?(\+)?=(.*)|;
@@ -148,10 +149,29 @@
     die "The following assets are missing:\n - 
$relevant_missing_assets\n$note\n";
 }
 
+sub _format_cmd_error ($command) {
+    return "Failed to execute '$command': $!" if $? == -1;
+    return ($? & 127)
+      ? sprintf "'$command' received signal %d", $? & 127
+      : sprintf "'$command' exited with non-zero exit status %d", $? >> 8;
+}
+
+sub _run_cmd ($command, @args) { system $command, @args; return $? == 0 ? '' : 
_format_cmd_error($command) }
+
+sub mirror ($url_handler, $from, $dst) {
+    my @curl_args = @{$url_handler->{curl_args}};
+    my $secrets = $url_handler->{secrets};
+    my $headers = Mojo::Headers->new;
+    OpenQA::UserAgent::add_auth_headers($headers, $from, @$secrets) if 
$secrets;
+    for my $name (@{$headers->names}) {
+        push @curl_args, '-H', "$name: " . $_ for 
@{$headers->every_header($name)};
+    }
+    _run_cmd CURL, @curl_args, qw(--continue-at - --output), $dst, $from;
+}
+
 sub clone_job_download_assets ($jobid, $job, $url_handler, $options) {
     my $parents = _get_chained_parents($job, $url_handler, $options);
     _check_for_missing_assets($job, $parents, $options);
-    my $ua = $url_handler->{ua};
     my $remote_url = $url_handler->{remote_url};
     for my $type (keys %{$job->{assets}}) {
         next if $type eq 'repo';    # we can't download repos
@@ -170,18 +190,13 @@
             $dst =~ s,.*/,,;
             my $dst_dir = path($options->{dir}, $type)->make_path;
             $dst = $dst_dir->child($dst)->to_string;
+            die "Cannot write $dst_dir\n" unless -w $dst_dir;
             my $from = $remote_url->clone;
             $from->path(sprintf '/tests/%d/asset/%s/%s', $jobid, $type, $file);
-            $from = $from->to_string;
-
-            die "Cannot write $dst_dir\n" unless -w $dst_dir;
-
             print STDERR "downloading\n$from\nto\n$dst\n";
-            my $r = $ua->mirror($from, $dst);
-            unless ($r->is_success || $r->code == HTTP_NOT_MODIFIED) {
-                print STDERR "$jobid failed: $file, ", $r->status_line, "\n";
-                die "Can't clone due to missing assets: ", $r->status_line, " 
\n"
-                  unless $options->{'ignore-missing-assets'};
+            if (my $error = mirror($url_handler, $from, $dst)) {
+                my $msg = "\nCloning aborted during asset download: $error\n";
+                $options->{'ignore-missing-assets'} ? print STDERR $msg : die 
$msg;
             }
 
             # ensure the asset cleanup preserves the asset the configured 
amount of days starting from the time
@@ -203,21 +218,18 @@
     return ($host_url, $jobid);
 }
 
-sub create_lwp_user_agent ($host, $options) {
-    my $ua = LWP::UserAgent->new;
-    $ua->timeout(10);
-    $ua->env_proxy;
-    $ua->show_progress(1) if $options->{'show-progress'};
-    return $ua unless my $cfg = OpenQA::UserAgent::open_config_file($host);
+sub make_curl_arguments ($options) {
+    my @args = ('--follow');
+    push @args, '--no-progress-meter' unless $options->{'show-progress'};
+    push @args, '--verbose' if $options->{verbose};
+    return \@args;
+}
 
+sub read_secrets ($host) {
+    return undef unless my $cfg = OpenQA::UserAgent::open_config_file($host);
     my $apikey = ($cfg->val($host, 'key'))[-1];
     my $apisecret = ($cfg->val($host, 'secret'))[-1];
-    $ua->add_handler(
-        request_prepare => sub ($request, $ua, $handler) {
-            OpenQA::UserAgent::add_auth_headers($request, 
Mojo::URL->new($request->uri), $apikey, $apisecret);
-        }) if $apikey && $apisecret;
-
-    return $ua;
+    return $apikey && $apisecret ? [$apikey, $apisecret] : undef;
 }
 
 sub create_url_handler ($options) {
@@ -234,9 +246,14 @@
     # configure user agents for the source host (usually a remote host)
     my $remote_url = OpenQA::Client::url_from_host($options->{from});
     $remote_url->path('/api/v1/jobs');
-    my $remote = OpenQA::Client->new(api => $remote_url->host);
-    my $ua = create_lwp_user_agent($remote_url->host, $options);
-    return {ua => $ua, local => $local, local_url => $local_url, remote => 
$remote, remote_url => $remote_url};
+    return {
+        curl_args => make_curl_arguments($options),
+        secrets => read_secrets($remote_url->host),
+        local => $local,
+        local_url => $local_url,
+        remote => OpenQA::Client->new(api => $remote_url->host),
+        remote_url => $remote_url
+    };
 }
 
 sub openqa_baseurl ($local_url) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1774473623.838c74ef/t/35-script_clone_job.t 
new/openQA-5.1774510397.efec10a7/t/35-script_clone_job.t
--- old/openQA-5.1774473623.838c74ef/t/35-script_clone_job.t    2026-03-25 
22:20:23.000000000 +0100
+++ new/openQA-5.1774510397.efec10a7/t/35-script_clone_job.t    2026-03-26 
08:33:17.000000000 +0100
@@ -19,8 +19,7 @@
 use Mojo::Transaction;
 use Scalar::Util qw(looks_like_number);
 
-# define fake client
-package Test::FakeLWPUserAgentMirrorResult {
+package Test::FakeResult {
     use Mojo::Base -base, -signatures;
     has is_success => 1;
     has code => 304;
@@ -28,31 +27,6 @@
     has json => undef;
 }    # uncoverable statement
 
-package Test::FakeLWPUserAgentMirrorTxn {
-    use Mojo::Base -base, -signatures;
-    has error => undef;
-    has res => sub { Test::FakeLWPUserAgentMirrorResult->new(is_success => 0, 
code => 404) };
-}    # uncoverable statement
-
-package Test::FakeLWPUserAgent {
-    use Mojo::Base -base, -signatures;
-    has mirrored => sub { {} };
-    has missing => 0;
-    has max_redirects => undef;
-    has fake_txn_args => sub { [] };
-
-    sub get ($self, $url) { 
Test::FakeLWPUserAgentMirrorTxn->new(@{$self->fake_txn_args}) }
-
-    sub mirror ($self, $from, $dest) {
-        my @res
-          = ($self->missing || $from !~ 
qr{http://foo/tests/1/asset/iso/(foo|bar)\.iso})
-          ? (is_success => 0, code => 404)
-          : ();
-        $self->mirrored->{$from} = $dest;
-        Test::FakeLWPUserAgentMirrorResult->new(@res);
-    }
-}    # uncoverable statement
-
 my @argv = (
     qw(WORKER_CLASS=local HDD_1=new.qcow2 HDDSIZEGB=40 FOO=value:with:colon),
     'WORKER_CLASS:cre:ate+hpc#foo@bar+=-parent'
@@ -73,15 +47,28 @@
     WORKER_CLASS => 'qemu_x86_64',
 );
 
+subtest 'running command' => sub {
+    my $run = \&OpenQA::Script::CloneJob::_run_cmd;
+    combined_like {
+        like $run->('does-not-exist'), qr/Failed.*does-not-exist.*No such/i, 
'error if command does not exist'
+    }
+    qr/can't exec/i, 'exec error logged';
+    like $run->('false'), qr/false.*exited.*1/i, 'error if command fails';
+    is $run->('true'), '', 'no error if command succeeds';
+};
+
 subtest 'getting job' => sub {
     my $clone_mock = Test::MockModule->new('OpenQA::Script::CloneJob');
     $clone_mock->redefine(_handle_unexpected_return_code => sub ($tx) { die 
'unexpected return code' });
-    my $url_handler = {remote => Test::FakeLWPUserAgent->new, remote_url => 
Mojo::URL->new('foo')};
+    my $fake_res = Test::FakeResult->new(is_success => 0, code => 400, json => 
{FOO => 'bar'});
+    my $fake_txn = Test::MockObject->new->set_always(res => 
$fake_res)->set_always(error => undef);
+    my $fake_ua = Test::MockObject->new->set_always(get => $fake_txn);
+    $fake_ua->set_always(max_redirects => $fake_ua);
+    my $url_handler = {remote => $fake_ua, remote_url => 
Mojo::URL->new('foo')};
     my $options = {'ignore-missing-assets' => 1, reproduce => 1};
     throws_ok { clone_job_get_job(42, $url_handler, $options) } qr/unexpected 
return code/,
       'unexpected return code handled';
-    my $fake_res = Test::FakeLWPUserAgentMirrorResult->new(is_success => 1, 
code => 200, json => {FOO => 'bar'});
-    $url_handler->{remote}->fake_txn_args([res => $fake_res]);
+    $fake_res->is_success(1)->code(200);
     my $job = clone_job_get_job(42, $url_handler, $options);
     is_deeply $job, {vars => {FOO => 'bar'}}, 'vars assigned' or 
always_explain $job;
 };
@@ -133,8 +120,10 @@
 
 subtest 'asset download' => sub {
     my $temp_assetdir = tempdir;
-    my $fake_ua = Test::FakeLWPUserAgent->new;
-    my %url_handler = (remote_url => Mojo::URL->new('http://foo'), ua => 
$fake_ua);
+    my $clone_mock = Test::MockModule->new('OpenQA::Script::CloneJob');
+    my %url_handler = (remote_url => Mojo::URL->new('http://foo'), curl_args 
=> []);
+    my %mirrored;
+    my $fake_download_error = 'fake-failure';
     my %options = (dir => $temp_assetdir);
     my $job_id = 1;
     my @missing_assets;
@@ -150,7 +139,7 @@
         missing_assets => \@missing_assets
     );
     $temp_assetdir->child($_)->make_path->chmod(0555) for qw(iso hdd);    # 
test with unwritable asset folders
-    my $clone_mock = Test::MockModule->new('OpenQA::Script::CloneJob');
+    $clone_mock->redefine(mirror => sub ($url_handler, $from, $dst) { 
$mirrored{$from} = $dst; $fake_download_error });
     $clone_mock->redefine(
         clone_job_get_job => sub ($job_id, $url_handler, $options) {
             return {id => 2, settings => {}, parents => {Chained => [3, 4]}} 
if $job_id eq 2;
@@ -170,12 +159,11 @@
 
     # assume an asset download fails
     $temp_assetdir->child($_)->remove_tree for qw(iso hdd);    # test with 
missing asset folders (will be created)
-    $fake_ua->missing(1);
     throws_ok {
         combined_like { clone_job_download_assets($job_id, \%job, 
\%url_handler, \%options) }
-        qr/downloading.*foo.*to.*failed.*some status/s, 'download error 
logged';
+        qr/downloading foobar/s, 'download logged';
     }
-    qr/Can't clone due to missing assets: some status/, 'error if asset does 
not exist';
+    qr|Cloning aborted during asset download:.*fake-failure|, 'error if asset 
does not exist';
 
     # assume openQA reports that an asset is missing
     @missing_assets = ('iso/foo.iso', 'hdd/some.qcow2');
@@ -197,35 +185,35 @@
     $options{'skip-deps'} = 0;
     $options{'ignore-missing-assets'} = 1;
     combined_like { clone_job_download_assets($job_id, \%job, \%url_handler, 
\%options) }
-    qr/downloading.*foo.*to.*failed.*some status.*downloading.*bar.*failed/s, 
'download error logged but ignored';
+    qr/Cloning aborted during asset download:.*fake-failure/s, 'download error 
logged but ignored';
 
-    $fake_ua->mirrored({})->missing(0);
+    %mirrored = ();
     combined_like { clone_job_download_assets($job_id, \%job, \%url_handler, 
\%options) }
     
qr{downloading.*http://.*foo.iso.*to.*foo.iso.*downloading.*http://.*bar.iso.*to.*bar.iso}s,
 'download logged';
-    is_deeply $fake_ua->mirrored, \%expected_downloads,
+    is_deeply \%mirrored, \%expected_downloads,
       'assets downloadeded except HDDs which are generated by parent job 
anyways'
-      or always_explain $fake_ua->mirrored;
+      or always_explain \%mirrored;
     ok -f "$temp_assetdir/iso/foo.iso", 'foo touched';
     ok -f "$temp_assetdir/iso/bar.iso", 'foo touched';
 
-    $fake_ua->mirrored({});
+    %mirrored = ();
     $options{'skip-deps'} = 1;
     $expected_downloads{"http://foo/tests/$job_id/asset/hdd/some.qcow2"} = 
"$temp_assetdir/hdd/some.qcow2";
     $expected_downloads{"http://foo/tests/$job_id/asset/hdd/uefi-vars.qcow2"} 
= "$temp_assetdir/hdd/uefi-vars.qcow2";
     combined_like { clone_job_download_assets($job_id, \%job, \%url_handler, 
\%options) } qr/downloading/,
       'downloading logged (1)';
-    is_deeply $fake_ua->mirrored, \%expected_downloads,
+    is_deeply \%mirrored, \%expected_downloads,
       'assets downloadeded including HDDs because we skip cloning the parent 
job'
-      or always_explain $fake_ua->mirrored;
+      or always_explain \%mirrored;
 
-    $fake_ua->mirrored({});
+    %mirrored = ();
     $job{parents} = {Chained => [3, 5]};
     delete 
$expected_downloads{"http://foo/tests/$job_id/asset/hdd/uefi-vars.qcow2"};
     combined_like { clone_job_download_assets($job_id, \%job, \%url_handler, 
\%options) } qr/downloading/,
       'downloading logged (2)';
-    is_deeply $fake_ua->mirrored, \%expected_downloads,
+    is_deeply \%mirrored, \%expected_downloads,
       'assets downloadeded except uefi-vars because no parent produces it 
anyways'
-      or always_explain $fake_ua->mirrored;
+      or always_explain \%mirrored;
 };
 
 subtest 'get 2 nodes HA cluster with get_deps' => sub {
@@ -496,17 +484,30 @@
     };
 };
 
-subtest 'auth with lwp' => sub {
-    note 'config path: ' . ($ENV{OPENQA_CONFIG} = "$FindBin::Bin/data");
-    my $ua = OpenQA::Script::CloneJob::create_lwp_user_agent('testapi', {});
-    $ua->add_handler(
-        request_send => sub ($request, $ua, $handler) {
-            ok looks_like_number($request->header('X-API-Microtime')), 
'microtime set';
-            is $request->header('X-API-Key'), 'PERCIVALKEY02', 'api key set';
-            is length($request->header('X-API-Hash')), 40, 'hash set';
-            return HTTP::Response->new(200);    # terminate the processing
+subtest 'invoking curl passing auth headers' => sub {
+    my $clone_mock = Test::MockModule->new('OpenQA::Script::CloneJob');
+    my @invoked_cmds;
+    $clone_mock->redefine(
+        _run_cmd => sub (@args) {
+            push @invoked_cmds, map { m/(.*hash:|.*time:)/i ? "$1 ?" : "$_" } 
@args;
+            '';
         });
-    $ua->get('http://foobar/some/path');
+    note 'config path: ' . ($ENV{OPENQA_CONFIG} = "$FindBin::Bin/data");
+
+    my $args = OpenQA::Script::CloneJob::make_curl_arguments({});
+    is_deeply $args, [qw(--follow --no-progress-meter)], 'default arguments 
correct';
+    my $secrets = OpenQA::Script::CloneJob::read_secrets('testapi');
+    is_deeply $secrets, [qw(PERCIVALKEY02 PERCIVALSECRET02)], 'key and secret 
as expected for host "testapi"';
+
+    my %url_handler = (curl_args => $args, secrets => $secrets);
+    my $error = OpenQA::Script::CloneJob::mirror(\%url_handler, 
Mojo::URL->new('url'), 'path');
+    my @expected_cmds = (
+        qw(curl --follow --no-progress-meter),
+        (map { -H => $_ } ('X-API-Hash: ?', 'X-API-Key: PERCIVALKEY02', 
'X-API-Microtime: ?')),
+        qw(--continue-at - --output path url),
+    );
+    is $error, '', 'no error returned';
+    is_deeply \@invoked_cmds, \@expected_cmds, 'invoked expected commands' or 
always_explain \@invoked_cmds;
 };
 
 subtest 'determining base URL' => sub {

++++++ openQA.obsinfo ++++++
--- /var/tmp/diff_new_pack.rmEv1r/_old  2026-03-27 16:52:32.251043119 +0100
+++ /var/tmp/diff_new_pack.rmEv1r/_new  2026-03-27 16:52:32.295044959 +0100
@@ -1,5 +1,5 @@
 name: openQA
-version: 5.1774473623.838c74ef
-mtime: 1774473623
-commit: 838c74ef0288862796840795d66bd78058e57ee5
+version: 5.1774510397.efec10a7
+mtime: 1774510397
+commit: efec10a7ca29ca75df50219ea57f9617419139f3
 

Reply via email to