#!perl
use 5.006;
use strict;
use warnings;
use Test::More;

BEGIN {
    use_ok( 'Net::SSLLabs' ) || print "Bail out!\n";
}

my $lab = Net::SSLLabs->new();
SKIP: {
	my $info;
	eval {
		$info = $lab->info();
	} or do {
		chomp $@;
		skip("Failed to info:$@", 4);
	};
	if (defined $info->version()) {
		ok($info->version() =~ /^\d+\.\d+\.\d+/smx, "\$info->version() is a major.minor.patch version number:" . $info->version());
	}
	ok($info->criteriaVersion() =~ /^2\d{3}\w*?/smx, "\$info->criteriaVersion() is a date based version number");
	ok($info->maxAssessments() =~ /^\d+/smx, "\$info->maxAssessments() is a number");
	ok($info->currentAssessments() =~ /^\d+/smx, "\$info->currentAssessments() is a number");
	ok(($info->messages())[0] =~ /^\w+ \w+/smx, "\$info->messages() is a list of messages");
}
my $hostName = 'ssllabs.com';
$hostName = 'au1.synchroad.com';
SKIP: {
	my $host;
	eval {
		while(not $host = $lab->analyze(host => $hostName)) {
		   sleep 10;
		}
		1;
	} or do {
		chomp $@;
		skip("Failed to analyze:$@", 500);
	};
	foreach my $endpoint ($host->endpoints()) {
	   ok(($host->host() . ' at ' . $endpoint->ipAddress() . ' gets a ' . $endpoint->grade()), "Synopis works - " . $host->host() . ' at ' . $endpoint->ipAddress() . ' gets a ' . $endpoint->grade());
	}
	ok($host->host() eq $hostName, "\$host->host() is correct");
	ok($host->port() == 443, "\$host->port() is correct");
	ok($host->startTime() =~ /^\d+/smx, "\$host->startTime() is a number");
	ok($host->criteriaVersion() =~ /^2\d{3}\w*?/smx, "\$host->criteriaVersion() is a date based version number");
	ok($host->engineVersion() =~ /^\d+\.\d+\.\d+/smx, "\$host->engineVersion() is a major.minor.patch version");
	ok($host->isPublic() =~ /^[01]$/smx, "\$host->isPublic() is a 0 or a 1");
	ok($host->status() eq 'READY', "\$host->status() is 'READY'");
	ok($host->protocol() eq 'HTTP', "\$host->protocol() is 'HTTP'");
	ok($host->testTime() =~ /^\d+/smx, "\$host->testTime() is a number");
	foreach my $endpoint ($host->endpoints()) {
		ok($endpoint->eta() =~ /^\d+$/smx, "\$endpoint->eta() is a number");
		ok($endpoint->gradeTrustIgnored() =~ /^[ABCDEF]\+?$/smx, "\$endpoint->gradeTrustIgnored() is an A+-F");
		ok($endpoint->duration() =~ /^\d+$/smx, "\$endpoint->duration() is a number");
		ok($endpoint->statusMessage() eq 'Ready', "\$endpoint->statusMessage() is 'Ready'");
		if (defined $endpoint->serverName()) {
			ok($endpoint->serverName() =~ /^[\w\.\-]+$/, "\$endpoint->serverName() looks like a fully qualified hostname");
		} else {
			ok(1, "\$endpoint->serverName() is not defined for '$host'");
		}
		ok($endpoint->delegation() =~ /^\d+$/smx, "\$endpoint->delegation() is a number");
		ok($endpoint->grade() =~ /^[ABCDEF]\+?$/smx, "\$endpoint->grade() is an A+-F.  '$host' gets a '" . $endpoint->grade() . "'");
		ok($endpoint->isExceptional() =~ /^[01]$/smx, "\$endpoint->isExceptional() is a 0 or a 1");
		ok($endpoint->hasWarnings() =~ /^[01]$/smx, "\$endpoint->hasWarnings() is a 0 or a 1");
	}
	my $endpointDetails = $lab->getEndpointData(host => $hostName, 's' => ($host->endpoints())[0]->ipAddress())->details();
	ok(ref $endpointDetails eq 'Net::SSLLabs::EndpointDetails', "\$lab->getEndpointData() returns a Net::SSLLabs::EndpointDetails object");
	my $suites = $endpointDetails->suites();
	ok(ref $suites eq 'Net::SSLLabs::Suites', "\$endpointDetails->suites() returns a Net::SSLLabs::Suites object");
	ok($suites->preference() =~ /^\d+/smx, "\$suites->preference() is a number");
	foreach my $suite ($suites->list()) {
		ok($suite->name() =~ /^[A-Z0-9_]+$/, "\$suite->name() looks like a suite name");
		ok($suite->id() =~ /^\d+$/, "\$suite->id() looks like a number");
		ok($suite->cipherStrength() =~ /^\d+$/, "\$suite->cipherStrength() looks like a number");
		if (defined $suite->ecdhStrength()) {
			ok($suite->ecdhStrength() =~ /^\d+$/, "\$suite->ecdhStrength() looks like a number");
			ok($suite->ecdhBits() =~ /^\d+$/, "\$suite->ecdhBits() looks like a number");
		} elsif (defined $suite->dhStrength()) {
			ok($suite->dhStrength() =~ /^\d+$/, "\$suite->dhStrength() looks like a number");
			ok($suite->dhP() =~ /^\d+$/, "\$suite->dhP() looks like a number");
			ok($suite->dhG() =~ /^\d+$/, "\$suite->dhG() looks like a number");
			ok($suite->dhYs() =~ /^\d+$/, "\$suite->dhYs() looks like a number");
		}
		last;
	}
	my $sims = $endpointDetails->sims();
	ok(ref $sims eq 'Net::SSLLabs::SimDetails', "\$endpointDetails->sims() returns a Net::SSLLabs::SimDetails object");
	foreach my $result ($sims->results()) {
		ok(ref $result eq 'Net::SSLLabs::Simulation', "\$sims->results() returns a list of Net::SSLLabs::Simulation objects");
		my $client = $result->client();
		ok(ref $client eq 'Net::SSLLabs::SimClient', "\$result->client() returns a Net::SSLLabs::SimClient object");
		ok($client->id() =~ /^\d+$/smx, "\$client->id() looks like a number");
		ok($client->name() =~ /^\w+ /smx, "\$client->name() looks like text");
		if (defined $client->platform()) {
			ok($client->platform() =~ /^\w+ /smx, "\$client->platform() looks like text:" . $client->platform());
		}
		ok($client->isReference() =~ /^[01]$/smx, "\$client->isReference() is a 0 or is a 1");
		ok($result->errorCode() =~ /^[01]$/smx, "\$result->errorCode() is a 0 or is a 1");
		ok($result->attempts() == 1, "\$result->attempts() is a 1");
		if (defined $result->protocolId()) {
			ok($result->protocolId() =~ /^\d+$/smx, "\$result->protocolId() is a number");
		}
		if (defined $result->suiteId()) {
			ok($result->suiteId() =~ /^\d+$/smx, "\$result->suiteId() is a number");
		}
	}
	foreach my $protocol ($endpointDetails->protocols()) {
		ok($protocol->id() =~ /^\d+$/smx, "\$protocol->id() looks like a number");
		ok($protocol->name() =~ /^(?:SSL|TLS)$/smx, "\$protocol->name() is SSL or TLS");
		ok($protocol->version() =~ /^\d+(?:\.\d+)?$/smx, "\$protocol->version() looks like SSL or TLS version number");
		if (defined $protocol->v2SuitesDisabled()) {
			ok($protocol->v2SuitesDisabled() == 1, "\$protocol->v2SuitesDisabled() is a 1");
		} else {
			ok(1, "\$protocol->v2SuitesDisabled() is unknown");
		}
		if (defined $protocol->q()) {
			ok($protocol->q() == 0, "\$protocol->q() is a 0");
		} else {
			ok(1, "\$protocol->q() is unknown");
		}
	}
	my $chain = $endpointDetails->chain();
	ok(ref $chain eq 'Net::SSLLabs::Chain', "\$endpointDetails->chain() returns a Net::SSLLabs::Chain object");
	ok($chain->issues() =~ /^\d+$/smx, "\$chain->issues() looks like a number");
	foreach my $chainCert ($chain->certs()) {
		ok(ref $chainCert eq 'Net::SSLLabs::ChainCert', "\$chain->certs() returns a list of Net::SSLLabs::ChainCert objects");
		ok($chainCert->subject() =~ /^\w+=\w+ \w+/smx, "\$chainCert->subject() looks like a distinguished name (DN)");
		ok($chainCert->label() =~ /^\w+/smx, "\$chainCert->label() is ok");
		ok($chainCert->notBefore() =~ /^\d+/smx, "\$chainCert->notBefore() is a number");
		ok($chainCert->notAfter() =~ /^\d+/smx, "\$chainCert->notAfter() is a number");
		ok($chainCert->issuerSubject() =~ /^\w+=\w+ \w+/smx, "\$chainCert->issuerSubject() looks like a distinguished name (DN)");
		ok($chainCert->issuerLabel() =~ /^\w \w+/smx, "\$chainCert->issuerLabel() looks like text");
		ok($chainCert->sigAlg() =~ /^\w+$/, "\$chainCert->sigAlg() looks good");
		ok($chainCert->issues() =~ /^\d+/smx, "\$chainCert->issues() is a number");
		ok($chainCert->keyAlg() =~ /^\w+$/, "\$chainCert->keyAlg() looks good");
		ok($chainCert->keySize() =~ /^\d+$/, "\$chainCert->keySize() is a number");
		ok($chainCert->keyStrength() =~ /^\d+$/, "\$chainCert->keyStrength() is a number");
		ok($chainCert->revocationStatus() =~ /^\d+/smx, "\$chainCert->revocationStatus() is a number");
		ok($chainCert->crlRevocationStatus() =~ /^\d+/smx, "\$chainCert->crlRevocationStatus() is a number");
		ok($chainCert->ocspRevocationStatus() =~ /^\d+/smx, "\$chainCert->ocspRevocationStatus() is a number");
		ok($chainCert->raw() =~ /^[-][-][-][-][-]BEGIN[ ]CERTIFICATE[-]/smx, "\$chainCert->raw() is a certificate");
	}
	my $key = $endpointDetails->key();
	ok(ref $key eq 'Net::SSLLabs::Key', "\$endpointDetails->key() returns a Net::SSLLabs::Key object");
	ok($key->size() =~ /^\d+$/smx, "\$key->size() is a number");
	ok($key->strength() =~ /^\d+$/smx, "\$key->strength() is a number");
	ok($key->alg() =~ /^(?:RSA|DSA|EC)$/smx, "\$key->alg() is correct");
	ok($key->debianFlaw() =~ /^[01]$/, "\$key->debianFlaw() is a 0 or is a 1");
	if (defined $key->q()) {
		ok($key->q() == 0, "\$key->q() is correct");
	}
	my $cert = $endpointDetails->cert();
	ok(ref $cert eq 'Net::SSLLabs::Cert', "\$endpointDetails->cert() returns a Net::SSLLabs::Cert object");
	ok($cert->issuerSubject() =~ /^\w+=\w+ \w+/smx, "\$cert->issuerSubject() looks like a distinguished name (DN)");
	ok($cert->issues() =~ /^\d+/smx, "\$cert->issues() is a number");
	ok(($cert->altNames())[0] eq $hostName, "\$cert->altNames() correctly returns a list, of which the first element is '$hostName'");
	ok($cert->ocspRevocationStatus() =~ /^\d+/smx, "\$cert->ocspRevocationStatus() is a number");
	ok(ref (($cert->ocspURIs())[0]) eq 'URI::http', "\$cert->ocspURIs() correctly returns a list of URI::http");
	ok($cert->revocationInfo() =~ /^\d+/smx, "\$cert->revocationInfo() is a number");
	ok($cert->sgc() =~ /^\d+/smx, "\$cert->sgc() is a number");
	if (defined $cert->validationType()) {
		ok($cert->validationType() =~ /^\w$/smx, "\$cert->validationType() is a number:" . $cert->validationType());
	} else {
		ok(1, "\$cert->validationType() is not defined for '$hostName'");
	}
	ok($cert->sct() =~ /^[01]$/, "\$cert->sct() is a 0 or a 1");
	ok($cert->sigAlg() =~ /^\w+$/, "\$cert->sigAlg() looks good");
	ok((($cert->commonNames())[0]) eq $hostName, "\$cert->commonNames() correctly returns a list of commonNames with the first equal to '$hostName'");
	ok(ref (($cert->crlURIs())[0]) eq 'URI::http', "\$cert->crlURIs() correctly returns a list of URI::http");
	ok($cert->issuerLabel() =~ /^\w+ \w+/smx, "\$cert->issuerLabel() looks like a description");
	ok($cert->subject() =~ /^\w+=\w+ \w+/smx, "\$cert->subject() looks like a distinguished name (DN)");
	ok($cert->notBefore() =~ /^\d+/smx, "\$cert->notBefore() is a number");
	ok($cert->revocationStatus() =~ /^\d+/smx, "\$cert->revocationStatus() is a number");
	ok($cert->notAfter() =~ /^\d+/smx, "\$cert->notAfter() is a number");
	ok($cert->crlRevocationStatus() =~ /^\d+/smx, "\$cert->crlRevocationStatus() is a number");
	ok($endpointDetails->supportsNpn() =~ /^[01]$/smx, "\$endpointDetails->supportsNpn() is a 0 or is a 1");
	ok($endpointDetails->poodleTls() =~ /^(?:-1|0|1|2)$/smx, "\$endpointDetails->poodleTls() is -1,0,1 or 2");
	if (defined $endpointDetails->ocspStapling()) {
		ok($endpointDetails->ocspStapling() =~ /^[01]$/smx, "\$endpointDetails->ocspStapling() is a 0 or is a 1");
	}
	ok($endpointDetails->poodle() =~ /^[01]$/smx, "\$endpointDetails->poodle() is a 0 or is a 1");
	ok($endpointDetails->stsMaxAge() =~ /^\d+$/smx, "\$endpointDetails->stsMaxAge() is a number");
	ok($endpointDetails->httpStatusCode() =~ /^[2345]\d{2}$/smx, "\$endpointDetails->stsMaxAge() is an http status code");
	ok($endpointDetails->supportsRc4() =~ /^[01]$/smx, "\$endpointDetails->supportsRc4() is a 0 or is a 1");
	ok($endpointDetails->compressionMethods() =~ /^\d+$/smx, "\$endpointDetails->compressionMethods() is a number");
	ok($endpointDetails->prefixDelegation() =~ /^[01]$/smx, "\$endpointDetails->prefixDelegation() is a 0 or is a 1");
	ok($endpointDetails->hostStartTime() =~ /^\d+$/smx, "\$endpointDetails->hostStartTime() is a number");
	ok($endpointDetails->forwardSecrecy() =~ /^\d+$/smx, "\$endpointDetails->forwardSecrecy() is a number");
	ok($endpointDetails->sniRequired() =~ /^[01]$/smx, "\$endpointDetails->sniRequired() is a 0 or is a 1");
	ok($endpointDetails->openSslCcs() =~ /^(?:-1|0|1|2|3)$/smx, "\$endpointDetails->openSslCcs() is -1,0,1,2 or 3");
	ok($endpointDetails->heartbeat() =~ /^[01]$/smx, "\$endpointDetails->heartbeat() is a 0 or is a 1");
	ok($endpointDetails->rc4WithModern() =~ /^[01]$/smx, "\$endpointDetails->rc4WithModern() is a 0 or is a 1");
	if (defined $endpointDetails->stsResponseHeader()) {
		ok($endpointDetails->stsResponseHeader() =~ /^max[-]age=\d+$/smx, "\$endpointDetails->stsResponseHeader() looks good");
	}
	ok($endpointDetails->stsSubdomains() =~ /^[01]$/smx, "\$endpointDetails->stsSubdomains() is a 0 or is a 1");
	ok($endpointDetails->vulnBeast() =~ /^[01]$/smx, "\$endpointDetails->vulnBeast() is a 0 or is a 1");
	ok($endpointDetails->fallbackScsv() =~ /^[01]$/smx, "\$endpointDetails->fallbackScsv() is a 0 or is a 1");
	ok($endpointDetails->heartbleed() =~ /^[01]$/smx, "\$endpointDetails->heartbleed() is a 0 or is a 1");
	ok($endpointDetails->freak() =~ /^[01]$/smx, "\$endpointDetails->freak() is a 0 or is a 1");
	if (defined $endpointDetails->staplingRevocationStatus()) {
		ok($endpointDetails->staplingRevocationStatus() =~ /^\d+/smx, "\$endpointDetails>staplingRevocationStatus() is a number");
	}
	ok($endpointDetails->nonPrefixDelegation() =~ /^[01]$/smx, "\$endpointDetails->nonPrefixDelegation() is a 0 or is a 1");
	ok(($endpointDetails->sessionTickets() =~ /^\d+$/smx) && ($endpointDetails->sessionTickets() < 8), "\$endpointDetails->sessionTickets() is a number less than 8");
	ok(($endpointDetails->hasSct() =~ /^\d+$/smx) && ($endpointDetails->hasSct() < 8), "\$endpointDetails->hasSct() is a number less than 8");
	if ($endpointDetails->renegSupport()) {
		ok(($endpointDetails->renegSupport() =~ /^\d+$/smx) && ($endpointDetails->renegSupport() < 16), "\$endpointDetails->renegSupport() is a number less than 16");
	}
	ok($endpointDetails->sessionResumption() =~ /^\d+$/smx && ($endpointDetails->sessionResumption() < 3), "\$endpointDetails->sessionResumption() is a number less than 3");
	ok($endpointDetails->serverSignature() =~ /^\w+/smx, "\$endpointDetails->serverSignature() looks good");
}

SKIP: {
	my $statusCodes;
	eval {
		$statusCodes = $lab->getStatusCodes();
	} or do {
		chomp $@;
		skip("Failed to getStatusCodes:$@", 1);
	};
	my %statusDetails = $statusCodes->statusDetails();
	ok($statusDetails{TESTING_HEARTBLEED} eq 'Testing Heartbleed', "\$statusCodes->statusDetails() correctly returns the English translation as a HASH");
}

done_testing();
