App-FuguWeb

 view release on metacpan or  search on metacpan

t/fuguweb/rotate.t  view on Meta::CPAN

	like( $error, qr/0 current keys/, 'and the status rules say why' );
	ok( !-e "$root/rel2.sec", 'and it generates no pair' );
};

subtest 'a trailing comment keeps its line' => sub {
	my $root = _keyed();
	my ($second) = _mint(
		$root,
		secret => "$root/rel2.sec",
		signer => "$root/root1.sec"
	);
	ok( $second, 'the second mint succeeds' ) or return;

	# Fugu::Config takes a comment behind a value, so the rewrite
	# must take one too. A writer that refused it would refuse a
	# description that the reader accepts.
	my $rc = Fugu::File->read("$root/.fuguwebrc");
	$rc =~ s/(key "fugubsd-1-release" \{\n\tstatus = current)/$1\t# the live key/
	    or die 'the fixture adds no comment';
	Fugu::File->write( "$root/.fuguwebrc", $rc );

	my ( $facts, $error ) = _promote(
		$root,
		signer   => "$root/root1.sec",
		retiring => "$root/rel1.sec"
	);
	ok( $facts, 'the promote succeeds' ) or diag($error);
	return unless $facts;

	$rc = Fugu::File->read("$root/.fuguwebrc");
	like( $rc, qr/status = retired\t\# the live key/,
		'the comment stands behind the new value' );
	is_deeply( [ _problems($root) ], [], 'the reader reports no problem' );
};

subtest 'an organization word that no key name carries fails' => sub {
	my $root = _site();

	# Fugu::KeyDir dies on such a word, and a caller of the
	# command reads a reason and an exit code.
	my $reason;
	my $config = App::FuguWeb::Config->load( root => $root,
		error => \$reason )
	    or die "load: $reason\n";
	my $rotate = App::FuguWeb::Rotate->new(
		config    => $config,
		org       => 'Fugu BSD',
		bootstrap => 1,
	);
	ok( !$rotate->mint( purpose => 'root', secret => "$root/k.sec" ),
		'the mint fails' );
	like( $rotate->error, qr/the organization word/,
		'and the reason names the word' );
};

subtest 'the key directory word names one directory' => sub {
	my $root = _site();

	# WEB-ROTATE-21. A word that held a solidus would write the
	# key outside the source directory.
	for my $dir ( '../escaped', 'a/b', '..', '.' ) {
		my $reason;
		my $config = App::FuguWeb::Config->load( root => $root,
			error => \$reason )
		    or die "load: $reason\n";
		my $rotate = App::FuguWeb::Rotate->new(
			config    => $config,
			org       => $ORG,
			dir       => $dir,
			bootstrap => 1,
		);
		ok(
			!$rotate->mint(
				purpose => 'root',
				secret  => "$root/k.sec"
			),
			"the word $dir fails the mint"
		);
		like( $rotate->error, qr/is not one name/,
			'and the reason says why' );
	}

	ok( !-e "$root/escaped", 'and no directory stands outside the source' );
};

# WEB-ROTATE-21. The directory word selects the key directory that a
# step writes. A description with one keys block needs none, and every
# subtest above reads that.
subtest 'a step names the key directory that it writes' => sub {
	my $root = _keyed();

	# The description holds no block of the second directory yet,
	# so the mint takes the intent, the name, the organization word
	# and the prefix, per WEB-ROTATE-15 and WEB-ROTATE-22.
	my $reason;
	my $config =
	    App::FuguWeb::Config->load( root => $root, error => \$reason )
	    or die "load: $reason\n";
	my $second = App::FuguWeb::Rotate->new(
		config    => $config,
		dir       => 'other',
		org       => 'other',
		url       => 'https://www.example.net/other',
		bootstrap => 1,
	);
	ok(
		$second->mint(
			purpose => 'root',
			secret  => "$root/other1.sec"
		),
		'the first root mint of a second directory succeeds'
	) or diag( $second->error );

	ok( -f "$root/web/other/other-1-root.pub",
		'the key lands in the directory that the caller named' );
	ok( -f "$root/web/other/SHA256", 'with a manifest of its own' );
	ok( !-e "$root/web/keys/other-1-root.pub",
		'and never in the first directory' );

	my $both =
	    App::FuguWeb::Config->load( root => $root, error => \$reason )
	    or die "load: $reason\n";
	is_deeply( [ $both->keys_dirs ],
		[ 'keys', 'other' ], 'the description names both directories' );

	# A description with several blocks names no one directory, so
	# a step that names none refuses before it writes.
	my $blind = App::FuguWeb::Rotate->new( config => $both );
	ok(
		!$blind->mint(
			purpose => 'release',
			secret  => "$root/blind.sec",
			signer  => "$root/root1.sec"
		),
		'a step that names no directory fails'
	);
	like(
		$blind->error,
		qr{the description holds the key directories keys and other, so the step needs --dir},
		'and the reason names each directory'
	);
	ok( !-e "$root/blind.sec", 'and the step writes no private half' );

t/fuguweb/rotate.t  view on Meta::CPAN

		'a mint of a directory that no keys block names fails'
	);
	like(
		$typo->error,
		qr{the description names no key directory keyz, and a mint or an import makes one with --bootstrap},
		'and the reason names the word that makes one'
	);

	# The refusal comes before the first write, so a mistyped word
	# leaves no directory and no key behind.
	ok( !-e "$root/web/keyz", 'and the source tree holds no such'
		    . ' directory' );
	ok( !-e "$root/typo.sec", 'and the step writes no private half' );
	unlike( Fugu::File->read("$root/.fuguwebrc"),
		qr/keyz/, 'and the description names no second block' );

	# The organization word reaches Fugu::KeyDir before the step
	# makes the directory, so a bootstrap that names none leaves no
	# empty directory either.
	my $nameless = App::FuguWeb::Rotate->new(
		config    => $config,
		dir       => 'other',
		bootstrap => 1,
	);
	ok(
		!$nameless->mint(
			purpose => 'root',
			secret  => "$root/other.sec"
		),
		'a bootstrap with no organization word fails'
	);
	like( $nameless->error, qr/needs the organization word/,
		'and the reason names the word' );
	ok( !-e "$root/web/other",
		'and the source tree holds no empty directory' );

	# The same step with the intent and the word makes the
	# directory.
	my $asked = App::FuguWeb::Rotate->new(
		config    => $config,
		dir       => 'other',
		org       => 'other',
		bootstrap => 1,
	);
	ok(
		$asked->mint(
			purpose => 'root',
			secret  => "$root/other.sec"
		),
		'a mint that states the intent succeeds'
	) or diag( $asked->error );
	ok( -f "$root/web/other/other-1-root.pub",
		'and the key lands in the new directory' );
};

# WEB-ROTATE-21 and WEB-KEYS-29. Every verb holds the word to one
# directory below the source directory.
subtest 'a promote names one key directory' => sub {
	my $root = _keyed();

	for my $dir ( '../escaped', 'a/b', '..', '.' ) {
		my $reason;
		my $config = App::FuguWeb::Config->load( root => $root,
			error => \$reason )
		    or die "load: $reason\n";
		my $rotate = App::FuguWeb::Rotate->new(
			config => $config,
			dir    => $dir,
		);
		ok(
			!$rotate->promote(
				purpose  => 'release',
				retiring => "$root/rel1.sec"
			),
			"the word $dir fails the promote"
		);
		like( $rotate->error, qr/is not one name/,
			'and the reason says why' );
	}

	ok( !-e "$root/escaped", 'and no directory stands outside the source' );
};

subtest 'an absent signify takes the code of a missing tool' => sub {
	my $root = _site();

	# WEB-ROTATE-17. A caller tells a tool that it must install
	# from a step that failed, as it does for a renderer.
	my ( $exit, $out, $err ) = do {
		local $ENV{PATH} = '/nonexistent';
		_run(
			'--project', $root, 'mint-key',
			'--purpose', 'root',
			'--secret',  "$root/root1.sec",
			'--org',     $ORG,
			'--bootstrap',
		);
	};
	is( $exit, 6, 'the command takes the missing tool code' );
	like( $err, qr/signify/, 'and the reason names the command' );
	ok( !-e "$root/root1.sec", 'and it writes no private half' );
};

subtest 'the verbs guard their options and print their facts' => sub {
	my $root = _site();

	# WEB-ROTATE-1. Each verb names the options that it needs.
	my %need = (
		'mint-key'    => [qw(purpose secret)],
		'import-key'  => [qw(purpose secret file)],
		'promote-key' => [qw(purpose retiring)],
	);

	for my $verb ( sort keys %need ) {
		for my $missing ( @{ $need{$verb} } ) {
			my %opt = map { $_ => "$root/value" }
			    @{ $need{$verb} };
			$opt{purpose} = 'release';
			delete $opt{$missing};

			my ( $exit, $out, $err ) =
			    _run( '--project', $root, $verb,
				map { ( "--$_", $opt{$_} ) } sort keys %opt );
			is( $exit, 2,
				"$verb: an absent --$missing takes the"
				    . ' argument code' );
			like( $err, qr/--\Q$missing\E is a necessary option/,
				'and the reason names it' );
		}
	}

	# WEB-ROTATE-13. One name=value line for each fact, so a
	# caller appends the output to a file that its steps read.
	my ( $exit, $out, $err ) = _run(
		'--project', $root, 'mint-key',
		'--purpose', 'root',
		'--secret',  "$root/root1.sec",
		'--org',     $ORG,
		'--url',     $URL,
		'--bootstrap',
	);



( run in 1.782 second using v1.01-cache-2.11-cpan-54e63673c56 )