Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
f7419a052b
15 changed files with 2155 additions and 1480 deletions
41
.github/workflows/ci.yml
vendored
41
.github/workflows/ci.yml
vendored
|
|
@ -33,6 +33,7 @@ jobs:
|
||||||
libtest-tcp-perl \
|
libtest-tcp-perl \
|
||||||
libtest-warnings-perl \
|
libtest-warnings-perl \
|
||||||
liburi-perl \
|
liburi-perl \
|
||||||
|
libwww-perl \
|
||||||
net-tools \
|
net-tools \
|
||||||
make \
|
make \
|
||||||
;
|
;
|
||||||
|
|
@ -61,33 +62,28 @@ jobs:
|
||||||
- fedora:39
|
- fedora:39
|
||||||
- fedora:latest
|
- fedora:latest
|
||||||
- fedora:rawhide
|
- fedora:rawhide
|
||||||
# RedHat UBI is mostly garbage due to a profound lack of basic
|
- almalinux:8
|
||||||
# packages. It is tested anyway because it's the closest available
|
- almalinux:latest
|
||||||
# approximation of RHEL. Some of the packages needed for some tests
|
|
||||||
# aren't available, so those tests will be skipped. I guess it's
|
|
||||||
# still better than nothing.
|
|
||||||
- registry.access.redhat.com/ubi7/ubi:latest
|
|
||||||
- registry.access.redhat.com/ubi8/ubi:latest
|
|
||||||
- registry.access.redhat.com/ubi9/ubi:latest
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: ${{ matrix.image }}
|
image: ${{ matrix.image }}
|
||||||
steps:
|
steps:
|
||||||
- if: ${{ matrix.image != 'registry.access.redhat.com/ubi7/ubi:latest' }}
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
- name: enable repositories (AlmaLinux 8)
|
||||||
# ubi7 is too old for checkout@v4.
|
if: ${{ matrix.image == 'almalinux:8' }}
|
||||||
- if: ${{ matrix.image == 'registry.access.redhat.com/ubi7/ubi:latest' }}
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: install dependencies
|
|
||||||
# The --skip-broken argument works around RedHat UBI's missing packages.
|
|
||||||
# (They're only used for testing, so it's OK to not install them.)
|
|
||||||
run: |
|
run: |
|
||||||
inst="dnf --refresh --skip-broken install -y"
|
dnf --refresh install -y 'dnf-command(config-manager)' epel-release &&
|
||||||
case '${{ matrix.image }}' in
|
dnf config-manager --set-enabled powertools
|
||||||
# RedHat UBI 7 (RHEL 7) doesn't have dnf.
|
- name: enable repositories (AlmaLinux latest)
|
||||||
*ubi7*) inst="yum --skip-broken install -y";;
|
if: ${{ matrix.image == 'almalinux:latest' }}
|
||||||
esac
|
run: |
|
||||||
${inst} \
|
dnf --refresh install -y 'dnf-command(config-manager)' epel-release &&
|
||||||
|
dnf config-manager --set-enabled crb
|
||||||
|
- name: install dependencies
|
||||||
|
# The --skip-broken argument works around missing packages. (They're
|
||||||
|
# only used for testing, so it's OK to not install them.)
|
||||||
|
run: |
|
||||||
|
dnf --refresh install --skip-broken -y \
|
||||||
automake \
|
automake \
|
||||||
findutils \
|
findutils \
|
||||||
iproute \
|
iproute \
|
||||||
|
|
@ -102,6 +98,7 @@ jobs:
|
||||||
perl-Test-TCP \
|
perl-Test-TCP \
|
||||||
perl-Test-Warnings \
|
perl-Test-Warnings \
|
||||||
perl-core \
|
perl-core \
|
||||||
|
perl-libwww-perl \
|
||||||
net-tools \
|
net-tools \
|
||||||
;
|
;
|
||||||
- name: autogen
|
- name: autogen
|
||||||
|
|
|
||||||
41
ChangeLog.md
41
ChangeLog.md
|
|
@ -3,18 +3,37 @@
|
||||||
This document describes notable changes. For details, see the [source code
|
This document describes notable changes. For details, see the [source code
|
||||||
repository history](https://github.com/ddclient/ddclient/commits/master).
|
repository history](https://github.com/ddclient/ddclient/commits/master).
|
||||||
|
|
||||||
## v3.11.3~alpha (unreleased work-in-progress)
|
## v4.0.0~alpha (unreleased work-in-progress)
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
|
|
||||||
|
* The `--ssl` option is now enabled by default.
|
||||||
|
[#705](https://github.com/ddclient/ddclient/pull/705)
|
||||||
* Unencrypted (plain) HTTP is now used instead of encrypted (TLS) HTTP if the
|
* Unencrypted (plain) HTTP is now used instead of encrypted (TLS) HTTP if the
|
||||||
URL uses `http://` instead of `https://`, even if the `--ssl` option is
|
URL uses `http://` instead of `https://`, even if the `--ssl` option is
|
||||||
enabled. [#608](https://github.com/ddclient/ddclient/pull/608)
|
enabled. [#608](https://github.com/ddclient/ddclient/pull/608)
|
||||||
|
* The `googledomains` built-in web IP discovery service
|
||||||
|
(`--webv4=googledomains`, `--webv6=googledomains`, and
|
||||||
|
`--web=googledomains`) is deprecated due to the service shutting down. It
|
||||||
|
will be removed in a future version of ddclient.
|
||||||
|
[5b104ad1](https://github.com/ddclient/ddclient/commit/5b104ad116c023c3760129cab6e141f04f72b406)
|
||||||
* The default web service for `--webv4` and `--webv6` has changed from Google
|
* The default web service for `--webv4` and `--webv6` has changed from Google
|
||||||
Domains (which is shutting down) to ipify.
|
Domains (which is shutting down) to ipify.
|
||||||
[5b104ad1](https://github.com/ddclient/ddclient/commit/5b104ad116c023c3760129cab6e141f04f72b406)
|
[5b104ad1](https://github.com/ddclient/ddclient/commit/5b104ad116c023c3760129cab6e141f04f72b406)
|
||||||
* All log messages are now written to STDERR, not a mix of STDOUT and STDERR.
|
* All log messages are now written to STDERR, not a mix of STDOUT and STDERR.
|
||||||
[#676](https://github.com/ddclient/ddclient/pull/676)
|
[#676](https://github.com/ddclient/ddclient/pull/676)
|
||||||
|
* For `--protocol=freedns` and `--protocol=nfsn`, the core module
|
||||||
|
`Digest::SHA` is now required. Previously, `Digest::SHA1` was used (if
|
||||||
|
available) as an alternative to `Digest::SHA`.
|
||||||
|
[#685](https://github.com/ddclient/ddclient/pull/685)
|
||||||
|
* The `he` built-in web IP discovery service (`--webv4=he`, `--webv6=he`, and
|
||||||
|
`--web=he`) was renamed to `he.net` for consistency with the new `he.net`
|
||||||
|
protocol. The old name is still accepted but is deprecated and will be
|
||||||
|
removed in a future version of ddclient.
|
||||||
|
[#682](https://github.com/ddclient/ddclient/pull/682)
|
||||||
|
* Deprecated built-in web IP discovery services are not listed in the output
|
||||||
|
of `--list-web-services`.
|
||||||
|
[#682](https://github.com/ddclient/ddclient/pull/682)
|
||||||
|
|
||||||
### New features
|
### New features
|
||||||
|
|
||||||
|
|
@ -41,6 +60,16 @@ repository history](https://github.com/ddclient/ddclient/commits/master).
|
||||||
* The second and subsequent lines in a multi-line log message are now prefixed
|
* The second and subsequent lines in a multi-line log message are now prefixed
|
||||||
with a `|` character.
|
with a `|` character.
|
||||||
[#676](https://github.com/ddclient/ddclient/pull/676)
|
[#676](https://github.com/ddclient/ddclient/pull/676)
|
||||||
|
* `emailonly`: New `protocol` option that simply emails you when your IP
|
||||||
|
address changes. [#654](https://github.com/ddclient/ddclient/pull/654)
|
||||||
|
* `he.net`: Added support for updating Hurricane Electric records.
|
||||||
|
[#682](https://github.com/ddclient/ddclient/pull/682)
|
||||||
|
* `dyndns2`, `domeneshop`, `dnsmadeeasy`, `keysystems`, `woima`: The `server`
|
||||||
|
option can now include `http://` or `https://` to control the use of TLS.
|
||||||
|
If omitted, the value of the `ssl` option is used to determine the scheme.
|
||||||
|
[#703](https://github.com/ddclient/ddclient/pull/703)
|
||||||
|
* `ddns.fm`: New `protocol` option for updating [DDNS.FM](https://ddns.fm/)
|
||||||
|
records. [#695](https://github.com/ddclient/ddclient/pull/695)
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
|
|
@ -73,6 +102,14 @@ repository history](https://github.com/ddclient/ddclient/commits/master).
|
||||||
[#667](https://github.com/ddclient/ddclient/pull/667)
|
[#667](https://github.com/ddclient/ddclient/pull/667)
|
||||||
* Fixed unnecessary repeated updates for some services.
|
* Fixed unnecessary repeated updates for some services.
|
||||||
[#670](https://github.com/ddclient/ddclient/pull/670)
|
[#670](https://github.com/ddclient/ddclient/pull/670)
|
||||||
|
* Fixed DNSExit provider when configured with a zone and non-identical
|
||||||
|
hostname. [#673](https://github.com/ddclient/ddclient/issues/673)
|
||||||
|
* `infomaniak`: Fixed frequent forced updates after 25 days (`max-interval`).
|
||||||
|
[#691](https://github.com/ddclient/ddclient/issues/691)
|
||||||
|
* `infomaniak`: Fixed incorrect parsing of server response.
|
||||||
|
[#692](https://github.com/ddclient/ddclient/issues/692)
|
||||||
|
* `regfishde`: Fixed IPv6 support.
|
||||||
|
[#691](https://github.com/ddclient/ddclient/issues/691)
|
||||||
|
|
||||||
## 2023-11-23 v3.11.2
|
## 2023-11-23 v3.11.2
|
||||||
|
|
||||||
|
|
@ -136,7 +173,7 @@ Refer to [v3.11 release plan discussions](https://github.com/ddclient/ddclient/i
|
||||||
|
|
||||||
* Added support for domaindiscount24.com
|
* Added support for domaindiscount24.com
|
||||||
* Added support for njal.la
|
* Added support for njal.la
|
||||||
|
|
||||||
## 2022-05-15 v3.10.0_2
|
## 2022-05-15 v3.10.0_2
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,13 @@ AM_PL_LOG_FLAGS = -Mstrict -w \
|
||||||
-MDevel::Autoflush
|
-MDevel::Autoflush
|
||||||
handwritten_tests = \
|
handwritten_tests = \
|
||||||
t/builtinfw_query.pl \
|
t/builtinfw_query.pl \
|
||||||
|
t/dnsexit2.pl \
|
||||||
t/get_ip_from_if.pl \
|
t/get_ip_from_if.pl \
|
||||||
t/geturl_connectivity.pl \
|
t/geturl_connectivity.pl \
|
||||||
|
t/geturl_response.pl \
|
||||||
|
t/group_hosts_by.pl \
|
||||||
|
t/header_ok.pl \
|
||||||
|
t/interval_expired.pl \
|
||||||
t/is-and-extract-ipv4.pl \
|
t/is-and-extract-ipv4.pl \
|
||||||
t/is-and-extract-ipv6.pl \
|
t/is-and-extract-ipv6.pl \
|
||||||
t/is-and-extract-ipv6-global.pl \
|
t/is-and-extract-ipv6-global.pl \
|
||||||
|
|
@ -72,7 +77,8 @@ handwritten_tests = \
|
||||||
t/parse_assignments.pl \
|
t/parse_assignments.pl \
|
||||||
t/skip.pl \
|
t/skip.pl \
|
||||||
t/ssl-validate.pl \
|
t/ssl-validate.pl \
|
||||||
t/write_cache.pl
|
t/variable_defaults.pl \
|
||||||
|
t/write_recap.pl
|
||||||
generated_tests = \
|
generated_tests = \
|
||||||
t/version.pl
|
t/version.pl
|
||||||
TESTS = $(handwritten_tests) $(generated_tests)
|
TESTS = $(handwritten_tests) $(generated_tests)
|
||||||
|
|
|
||||||
14
README.md
14
README.md
|
|
@ -17,11 +17,13 @@ Dynamic DNS services currently supported include:
|
||||||
* [ChangeIP](https://www.changeip.com)
|
* [ChangeIP](https://www.changeip.com)
|
||||||
* [CloudFlare](https://www.cloudflare.com)
|
* [CloudFlare](https://www.cloudflare.com)
|
||||||
* [ClouDNS](https://www.cloudns.net)
|
* [ClouDNS](https://www.cloudns.net)
|
||||||
|
* [DDNS.fm](https://www.ddns.fm/)
|
||||||
* [DigitalOcean](https://www.digitalocean.com/)
|
* [DigitalOcean](https://www.digitalocean.com/)
|
||||||
* [dinahosting](https://dinahosting.com)
|
* [dinahosting](https://dinahosting.com)
|
||||||
* [DonDominio](https://www.dondominio.com)
|
* [DonDominio](https://www.dondominio.com)
|
||||||
* [DNS Made Easy](https://dnsmadeeasy.com)
|
* [DNS Made Easy](https://dnsmadeeasy.com)
|
||||||
* [DNSExit](https://dnsexit.com/dns/dns-api)
|
* [DNSExit](https://dnsexit.com/dns/dns-api)
|
||||||
|
* [dnsHome.de](https://www.dnshome.de)
|
||||||
* [Domeneshop](https://api.domeneshop.no/docs/#tag/ddns/paths/~1dyndns~1update/get)
|
* [Domeneshop](https://api.domeneshop.no/docs/#tag/ddns/paths/~1dyndns~1update/get)
|
||||||
* [DslReports](https://www.dslreports.com)
|
* [DslReports](https://www.dslreports.com)
|
||||||
* [Duck DNS](https://duckdns.org)
|
* [Duck DNS](https://duckdns.org)
|
||||||
|
|
@ -33,6 +35,7 @@ Dynamic DNS services currently supported include:
|
||||||
* [Gandi](https://gandi.net)
|
* [Gandi](https://gandi.net)
|
||||||
* [GoDaddy](https://www.godaddy.com)
|
* [GoDaddy](https://www.godaddy.com)
|
||||||
* [Google](https://domains.google)
|
* [Google](https://domains.google)
|
||||||
|
* [Hurricane Electric](https://dns.he.net)
|
||||||
* [Infomaniak](https://faq.infomaniak.com/2376)
|
* [Infomaniak](https://faq.infomaniak.com/2376)
|
||||||
* [Loopia](https://www.loopia.se)
|
* [Loopia](https://www.loopia.se)
|
||||||
* [Mythic Beasts](https://www.mythic-beasts.com/support/api/dnsv2/dynamic-dns)
|
* [Mythic Beasts](https://www.mythic-beasts.com/support/api/dnsv2/dynamic-dns)
|
||||||
|
|
@ -130,9 +133,16 @@ Note that any issues prior to version v3.9.1 will not be listed here.
|
||||||
If a fix is committed but not yet part of any tagged release, the notes here will reference the not-yet-released version number.
|
If a fix is committed but not yet part of any tagged release, the notes here will reference the not-yet-released version number.
|
||||||
|
|
||||||
### v3.11.2 - v3.9.1: SSL parameter breaks HTTP-only IP acquisition
|
### v3.11.2 - v3.9.1: SSL parameter breaks HTTP-only IP acquisition
|
||||||
The `ssl` parameter forces all connections to use HTTPS. While technically working as expected, this behavior keeps coming up as a pain point when using HTTP-only IP querying sites such as http://checkip.dyndns.org. For the future (v3.11.3), the behavior is changed to respect `http://` in a URL. A separate parameter to disallow all HTTP connections or warn about them may be added later.
|
|
||||||
|
|
||||||
**Fix**: v3.11.3 will use HTTP to connect to URLs starting with `http://`. See [here](https://github.com/ddclient/ddclient/pull/608) for more info.
|
The `ssl` parameter forces all connections to use HTTPS. While technically
|
||||||
|
working as expected, this behavior keeps coming up as a pain point when using
|
||||||
|
HTTP-only IP querying sites such as http://checkip.dyndns.org. Starting with
|
||||||
|
v4.0.0, the behavior is changed to respect `http://` in a URL. A separate
|
||||||
|
parameter to disallow all HTTP connections or warn about them may be added
|
||||||
|
later.
|
||||||
|
|
||||||
|
**Fix**: v4.0.0 uses HTTP to connect to URLs starting with `http://`. See
|
||||||
|
[here](https://github.com/ddclient/ddclient/pull/608) for more info.
|
||||||
|
|
||||||
**Workaround**: Disable the SSL parameter
|
**Workaround**: Disable the SSL parameter
|
||||||
|
|
||||||
|
|
|
||||||
17
configure.ac
17
configure.ac
|
|
@ -36,7 +36,18 @@ AC_PROG_MKDIR_P
|
||||||
AC_PATH_PROG([FIND], [find])
|
AC_PATH_PROG([FIND], [find])
|
||||||
AS_IF([test -z "${FIND}"], [AC_MSG_ERROR(['find' utility not found])])
|
AS_IF([test -z "${FIND}"], [AC_MSG_ERROR(['find' utility not found])])
|
||||||
|
|
||||||
AC_PATH_PROG([CURL], [curl])
|
AC_ARG_WITH([curl],
|
||||||
|
[AS_HELP_STRING([[--with-curl[=CURL]]], [use CURL as absolute path to curl executable])],
|
||||||
|
[],
|
||||||
|
[with_curl=yes])
|
||||||
|
AS_CASE([${with_curl}],
|
||||||
|
[[yes]], [AC_PATH_PROG([CURL], [curl])],
|
||||||
|
[[no]], [CURL=],
|
||||||
|
[
|
||||||
|
AC_MSG_CHECKING([for curl])
|
||||||
|
CURL=${with_curl}
|
||||||
|
AC_MSG_RESULT([${CURL}])
|
||||||
|
]);
|
||||||
AS_IF([test -z "${CURL}"], [AC_MSG_ERROR([curl not found])])
|
AS_IF([test -z "${CURL}"], [AC_MSG_ERROR([curl not found])])
|
||||||
|
|
||||||
AX_WITH_PROG([PERL], perl)
|
AX_WITH_PROG([PERL], perl)
|
||||||
|
|
@ -49,6 +60,7 @@ AC_SUBST([PERL])
|
||||||
# package doesn't depend on all of them, so their availability can't
|
# package doesn't depend on all of them, so their availability can't
|
||||||
# be assumed.
|
# be assumed.
|
||||||
m4_foreach_w([_m], [
|
m4_foreach_w([_m], [
|
||||||
|
Data::Dumper
|
||||||
File::Basename
|
File::Basename
|
||||||
File::Path
|
File::Path
|
||||||
File::Temp
|
File::Temp
|
||||||
|
|
@ -63,7 +75,6 @@ m4_foreach_w([_m], [
|
||||||
# then some tests will fail. Only prints a warning if not installed.
|
# then some tests will fail. Only prints a warning if not installed.
|
||||||
m4_foreach_w([_m], [
|
m4_foreach_w([_m], [
|
||||||
B
|
B
|
||||||
Data::Dumper
|
|
||||||
File::Spec::Functions
|
File::Spec::Functions
|
||||||
File::Temp
|
File::Temp
|
||||||
], [AX_PROG_PERL_MODULES([_m], [],
|
], [AX_PROG_PERL_MODULES([_m], [],
|
||||||
|
|
@ -80,6 +91,8 @@ m4_foreach_w([_m], [
|
||||||
HTTP::Message::PSGI
|
HTTP::Message::PSGI
|
||||||
HTTP::Request
|
HTTP::Request
|
||||||
HTTP::Response
|
HTTP::Response
|
||||||
|
JSON::PP
|
||||||
|
LWP::UserAgent
|
||||||
Scalar::Util
|
Scalar::Util
|
||||||
Test::MockModule
|
Test::MockModule
|
||||||
Test::TCP
|
Test::TCP
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,16 @@
|
||||||
## are mentioned here.
|
## are mentioned here.
|
||||||
##
|
##
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
## Use encryption (TLS) when the scheme (either "http://" or "https://") is
|
||||||
|
## missing from a URL. Defaults to "yes".
|
||||||
|
#ssl=yes
|
||||||
|
|
||||||
daemon=300 # check every 300 seconds
|
daemon=300 # check every 300 seconds
|
||||||
syslog=yes # log update msgs to syslog
|
syslog=yes # log update msgs to syslog
|
||||||
mail=root # mail all msgs to root
|
mail=root # mail all msgs to root
|
||||||
mail-failure=root # mail failed update msgs to root
|
mail-failure=root # mail failed update msgs to root
|
||||||
pid=@runstatedir@/ddclient.pid # record PID in file.
|
pid=@runstatedir@/ddclient.pid # record PID in file.
|
||||||
ssl=yes # use ssl-support. Works with
|
|
||||||
# ssl-library
|
|
||||||
# postscript=script # run script after updating. The
|
# postscript=script # run script after updating. The
|
||||||
# new IP is added as argument.
|
# new IP is added as argument.
|
||||||
#
|
#
|
||||||
|
|
@ -222,6 +225,13 @@ ssl=yes # use ssl-support. Works with
|
||||||
# password=my-auto-generated-password
|
# password=my-auto-generated-password
|
||||||
# my.domain.tld, otherhost.domain.tld
|
# my.domain.tld, otherhost.domain.tld
|
||||||
|
|
||||||
|
##
|
||||||
|
## Hurricane Electric (dns.he.net)
|
||||||
|
##
|
||||||
|
# protocol=he.net, \
|
||||||
|
# password=my-genereated-password \
|
||||||
|
# myhost.example.com
|
||||||
|
|
||||||
##
|
##
|
||||||
## Duckdns (http://www.duckdns.org/)
|
## Duckdns (http://www.duckdns.org/)
|
||||||
##
|
##
|
||||||
|
|
@ -238,6 +248,14 @@ ssl=yes # use ssl-support. Works with
|
||||||
# password=my-token
|
# password=my-token
|
||||||
# myhost
|
# myhost
|
||||||
|
|
||||||
|
##
|
||||||
|
## DDNS.FM (https://ddns.fm/)
|
||||||
|
##
|
||||||
|
#
|
||||||
|
# protocol=ddns.fm,
|
||||||
|
# password=my-token
|
||||||
|
# myhost.example.com
|
||||||
|
|
||||||
##
|
##
|
||||||
## MyOnlinePortal (http://myonlineportal.net)
|
## MyOnlinePortal (http://myonlineportal.net)
|
||||||
##
|
##
|
||||||
|
|
@ -391,3 +409,18 @@ ssl=yes # use ssl-support. Works with
|
||||||
# password=ddns_password
|
# password=ddns_password
|
||||||
# redirect=2
|
# redirect=2
|
||||||
# example.com
|
# example.com
|
||||||
|
|
||||||
|
##
|
||||||
|
## Email Only
|
||||||
|
##
|
||||||
|
# protocol=emailonly
|
||||||
|
# host.example.com
|
||||||
|
|
||||||
|
##
|
||||||
|
## dnsHome.de
|
||||||
|
##
|
||||||
|
# protocol=dyndns2 \
|
||||||
|
# server=www.dnshome.de \
|
||||||
|
# login=subdomain.domain.tld \
|
||||||
|
# password=your_password \
|
||||||
|
# subdomain.domain.tld
|
||||||
|
|
|
||||||
2943
ddclient.in
2943
ddclient.in
File diff suppressed because it is too large
Load diff
203
t/dnsexit2.pl
Normal file
203
t/dnsexit2.pl
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
use Test::More;
|
||||||
|
eval { require JSON::PP; } or plan(skip_all => $@);
|
||||||
|
JSON::PP->import(qw(encode_json decode_json));
|
||||||
|
eval { require 'ddclient'; } or BAIL_OUT($@);
|
||||||
|
eval { require ddclient::Test::Fake::HTTPD; } or plan(skip_all => $@);
|
||||||
|
eval { require LWP::UserAgent; } or plan(skip_all => $@);
|
||||||
|
|
||||||
|
ddclient::load_json_support('dnsexit2');
|
||||||
|
|
||||||
|
my @requests; # Declare global variable to store requests, used for tests.
|
||||||
|
my @httpd_requests; # Declare variable specificly used for the httpd process (which cannot be shared with tests).
|
||||||
|
my $httpd = ddclient::Test::Fake::HTTPD->new();
|
||||||
|
|
||||||
|
$httpd->run(sub {
|
||||||
|
my ($req) = @_;
|
||||||
|
if ($req->uri->as_string eq '/get_requests') {
|
||||||
|
return [200, ['Content-Type' => 'application/json'], [encode_json(\@httpd_requests)]];
|
||||||
|
} elsif ($req->uri->as_string eq '/reset_requests') {
|
||||||
|
@httpd_requests = ();
|
||||||
|
return [200, ['Content-Type' => 'application/json'], [encode_json({ message => 'OK' })]];
|
||||||
|
}
|
||||||
|
my $request_info = {
|
||||||
|
method => $req->method,
|
||||||
|
uri => $req->uri->as_string,
|
||||||
|
content => $req->content,
|
||||||
|
headers => $req->headers->as_string
|
||||||
|
};
|
||||||
|
push @httpd_requests, $request_info;
|
||||||
|
return [200, ['Content-Type' => 'application/json'], [encode_json({
|
||||||
|
code => 0,
|
||||||
|
message => 'Success'
|
||||||
|
})]];
|
||||||
|
});
|
||||||
|
|
||||||
|
diag(sprintf("started IPv4 server running at %s", $httpd->endpoint()));
|
||||||
|
|
||||||
|
my $ua = LWP::UserAgent->new;
|
||||||
|
|
||||||
|
sub test_nic_dnsexit2_update {
|
||||||
|
my ($config, @hostnames) = @_;
|
||||||
|
%ddclient::config = %$config;
|
||||||
|
ddclient::nic_dnsexit2_update(@hostnames);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub decode_and_sort_array {
|
||||||
|
my ($data) = @_;
|
||||||
|
if (!ref $data) {
|
||||||
|
$data = decode_json($data);
|
||||||
|
}
|
||||||
|
@{$data->{update}} = sort { $a->{type} cmp $b->{type} } @{$data->{update}};
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub reset_test_data {
|
||||||
|
my $response = $ua->get($httpd->endpoint . '/reset_requests');
|
||||||
|
die "Failed to reset requests" unless $response->is_success;
|
||||||
|
@requests = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_requests {
|
||||||
|
my $res = $ua->get($httpd->endpoint . '/get_requests');
|
||||||
|
die "Failed to get requests: " . $res->status_line unless $res->is_success;
|
||||||
|
return @{decode_json($res->decoded_content)};
|
||||||
|
}
|
||||||
|
|
||||||
|
subtest 'Testing nic_dnsexit2_update' => sub {
|
||||||
|
my %config = (
|
||||||
|
'host.my.zone.com' => {
|
||||||
|
'ssl' => 'no',
|
||||||
|
'verbose' => 'yes',
|
||||||
|
'usev4' => 'ipv4',
|
||||||
|
'wantipv4' => '8.8.4.4',
|
||||||
|
'usev6' => 'ipv6',
|
||||||
|
'wantipv6' => '2001:4860:4860::8888',
|
||||||
|
'protocol' => 'dnsexit2',
|
||||||
|
'password' => 'mytestingpassword',
|
||||||
|
'zone' => 'my.zone.com',
|
||||||
|
'server' => $httpd->host_port(),
|
||||||
|
'path' => '/update',
|
||||||
|
'ttl' => 5
|
||||||
|
});
|
||||||
|
test_nic_dnsexit2_update(\%config, 'host.my.zone.com');
|
||||||
|
@requests = get_requests();
|
||||||
|
is($requests[0]->{method}, 'POST', 'Method is correct');
|
||||||
|
is($requests[0]->{uri}, '/update', 'URI contains correct path');
|
||||||
|
like($requests[0]->{headers}, qr/Content-Type: application\/json/, 'Content-Type header is correct');
|
||||||
|
like($requests[0]->{headers}, qr/Accept: application\/json/, 'Accept header is correct');
|
||||||
|
my $data = decode_and_sort_array($requests[0]->{content});
|
||||||
|
my $expected_data = decode_and_sort_array({
|
||||||
|
'domain' => 'my.zone.com',
|
||||||
|
'apikey' => 'mytestingpassword',
|
||||||
|
'update' => [
|
||||||
|
{
|
||||||
|
'type' => 'A',
|
||||||
|
'name' => 'host',
|
||||||
|
'content' => '8.8.4.4',
|
||||||
|
'ttl' => 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type' => 'AAAA',
|
||||||
|
'name' => 'host',
|
||||||
|
'content' => '2001:4860:4860::8888',
|
||||||
|
'ttl' => 5,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
is_deeply($data, $expected_data, 'Data is correct');
|
||||||
|
reset_test_data();
|
||||||
|
};
|
||||||
|
|
||||||
|
subtest 'Testing nic_dnsexit2_update without a zone set' => sub {
|
||||||
|
my %config = (
|
||||||
|
'myhost.zone.com' => {
|
||||||
|
'ssl' => 'yes',
|
||||||
|
'verbose' => 'yes',
|
||||||
|
'usev4' => 'ipv4',
|
||||||
|
'wantipv4' => '8.8.4.4',
|
||||||
|
'protocol' => 'dnsexit2',
|
||||||
|
'password' => 'anotherpassword',
|
||||||
|
'server' => $httpd->host_port(),
|
||||||
|
'path' => '/update-alt',
|
||||||
|
'ttl' => 10
|
||||||
|
});
|
||||||
|
test_nic_dnsexit2_update(\%config, 'myhost.zone.com');
|
||||||
|
@requests = get_requests();
|
||||||
|
my $data = decode_and_sort_array($requests[0]->{content});
|
||||||
|
my $expected_data = decode_and_sort_array({
|
||||||
|
'domain' => 'myhost.zone.com',
|
||||||
|
'apikey' => 'anotherpassword',
|
||||||
|
'update' => [
|
||||||
|
{
|
||||||
|
'type' => 'A',
|
||||||
|
'name' => '',
|
||||||
|
'content' => '8.8.4.4',
|
||||||
|
'ttl' => 10,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
is_deeply($data, $expected_data, 'Data is correct');
|
||||||
|
reset_test_data($ua);
|
||||||
|
};
|
||||||
|
|
||||||
|
subtest 'Testing nic_dnsexit2_update with two hostnames, one with a zone and one without' => sub {
|
||||||
|
my %config = (
|
||||||
|
'host1.zone.com' => {
|
||||||
|
'ssl' => 'yes',
|
||||||
|
'verbose' => 'yes',
|
||||||
|
'usev4' => 'ipv4',
|
||||||
|
'wantipv4' => '8.8.4.4',
|
||||||
|
'protocol' => 'dnsexit2',
|
||||||
|
'password' => 'testingpassword',
|
||||||
|
'server' => $httpd->host_port(),
|
||||||
|
'path' => '/update',
|
||||||
|
'ttl' => 5
|
||||||
|
},
|
||||||
|
'host2.zone.com' => {
|
||||||
|
'ssl' => 'yes',
|
||||||
|
'verbose' => 'yes',
|
||||||
|
'usev6' => 'ipv6',
|
||||||
|
'wantipv6' => '2001:4860:4860::8888',
|
||||||
|
'protocol' => 'dnsexit2',
|
||||||
|
'password' => 'testingpassword',
|
||||||
|
'server' => $httpd->host_port(),
|
||||||
|
'path' => '/update',
|
||||||
|
'ttl' => 10,
|
||||||
|
'zone' => 'zone.com'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
test_nic_dnsexit2_update(\%config, 'host1.zone.com', 'host2.zone.com');
|
||||||
|
my $expected_data1 = decode_and_sort_array({
|
||||||
|
'domain' => 'host1.zone.com',
|
||||||
|
'apikey' => 'testingpassword',
|
||||||
|
'update' => [
|
||||||
|
{
|
||||||
|
'type' => 'A',
|
||||||
|
'name' => '',
|
||||||
|
'content' => '8.8.4.4',
|
||||||
|
'ttl' => 5,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
my $expected_data2 = decode_and_sort_array({
|
||||||
|
'domain' => 'zone.com',
|
||||||
|
'apikey' => 'testingpassword',
|
||||||
|
'update' => [
|
||||||
|
{
|
||||||
|
'type' => 'AAAA',
|
||||||
|
'name' => 'host2',
|
||||||
|
'content' => '2001:4860:4860::8888',
|
||||||
|
'ttl' => 10,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
@requests = get_requests();
|
||||||
|
for my $i (0..1) {
|
||||||
|
my $data = decode_and_sort_array($requests[$i]->{content});
|
||||||
|
is_deeply($data, $expected_data1, 'Data is correct for call host1') if $i == 0;
|
||||||
|
is_deeply($data, $expected_data2, 'Data is correct for call host2') if $i == 1;
|
||||||
|
}
|
||||||
|
reset_test_data();
|
||||||
|
};
|
||||||
|
|
||||||
|
done_testing();
|
||||||
|
|
@ -39,23 +39,30 @@ subtest "get_ip_from_interface tests" => sub {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
subtest "Get default interface and IP for test system" => sub {
|
subtest "Get default interface and IP for test system (IPv4)" => sub {
|
||||||
my $interface = ddclient::get_default_interface(4);
|
my $interface = ddclient::get_default_interface(4);
|
||||||
if ($interface) {
|
plan(skip_all => 'no IPv4 interface') if !$interface;
|
||||||
isnt($interface, "lo", "Check for loopback 'lo'");
|
isnt($interface, "lo", "Check for loopback 'lo'");
|
||||||
isnt($interface, "lo0", "Check for loopback 'lo0'");
|
isnt($interface, "lo0", "Check for loopback 'lo0'");
|
||||||
my $ip1 = ddclient::get_ip_from_interface("default", 4);
|
my $ip1 = ddclient::get_ip_from_interface("default", 4);
|
||||||
my $ip2 = ddclient::get_ip_from_interface($interface, 4);
|
my $ip2 = ddclient::get_ip_from_interface($interface, 4);
|
||||||
is($ip1, $ip2, "Check IPv4 from default interface");
|
is($ip1, $ip2, "Check IPv4 from default interface");
|
||||||
|
SKIP: {
|
||||||
|
skip('default interface does not have an appropriate IPv4 addresses') if !$ip1;
|
||||||
ok(ddclient::is_ipv4($ip1), "Valid IPv4 from get_ip_from_interface($interface)");
|
ok(ddclient::is_ipv4($ip1), "Valid IPv4 from get_ip_from_interface($interface)");
|
||||||
}
|
}
|
||||||
$interface = ddclient::get_default_interface(6);
|
};
|
||||||
if ($interface) {
|
|
||||||
isnt($interface, "lo", "Check for loopback 'lo'");
|
subtest "Get default interface and IP for test system (IPv6)" => sub {
|
||||||
isnt($interface, "lo0", "Check for loopback 'lo0'");
|
my $interface = ddclient::get_default_interface(6);
|
||||||
my $ip1 = ddclient::get_ip_from_interface("default", 6);
|
plan(skip_all => 'no IPv6 interface') if !$interface;
|
||||||
my $ip2 = ddclient::get_ip_from_interface($interface, 6);
|
isnt($interface, "lo", "Check for loopback 'lo'");
|
||||||
is($ip1, $ip2, "Check IPv6 from default interface");
|
isnt($interface, "lo0", "Check for loopback 'lo0'");
|
||||||
|
my $ip1 = ddclient::get_ip_from_interface("default", 6);
|
||||||
|
my $ip2 = ddclient::get_ip_from_interface($interface, 6);
|
||||||
|
is($ip1, $ip2, "Check IPv6 from default interface");
|
||||||
|
SKIP: {
|
||||||
|
skip('default interface does not have an appropriate IPv6 addresses') if !$ip1;
|
||||||
ok(ddclient::is_ipv6($ip1), "Valid IPv6 from get_ip_from_interface($interface)");
|
ok(ddclient::is_ipv6($ip1), "Valid IPv6 from get_ip_from_interface($interface)");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
27
t/geturl_response.pl
Normal file
27
t/geturl_response.pl
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use Test::More;
|
||||||
|
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
|
||||||
|
eval { require 'ddclient'; } or BAIL_OUT($@);
|
||||||
|
|
||||||
|
# Fake curl. Use the printf utility, which can process escapes. This allows Perl to drive the fake
|
||||||
|
# curl with plain ASCII and get arbitrary bytes back, avoiding problems caused by any encoding that
|
||||||
|
# might be done by Perl (e.g., "use open ':encoding(UTF-8)';").
|
||||||
|
my @fakecurl = ('sh', '-c', 'printf %b "$1"', '--');
|
||||||
|
|
||||||
|
my @test_cases = (
|
||||||
|
{
|
||||||
|
desc => 'binary body',
|
||||||
|
# Body is UTF-8 encoded ✨ (U+2728 Sparkles) followed by a 0xff byte (invalid UTF-8).
|
||||||
|
printf => join('\r\n', ('HTTP/1.1 200 OK', '', '\0342\0234\0250\0377')),
|
||||||
|
# The raw bytes should come through as equally valued codepoints. They must not be decoded.
|
||||||
|
want => "HTTP/1.1 200 OK\n\n\xe2\x9c\xa8\xff",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
for my $tc (@test_cases) {
|
||||||
|
@ddclient::curl = (@fakecurl, $tc->{printf});
|
||||||
|
$ddclient::curl if 0; # suppress spurious warning "Name used only once: possible typo"
|
||||||
|
my $got = ddclient::geturl(url => 'http://ignored');
|
||||||
|
is($got, $tc->{want}, $tc->{desc});
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing();
|
||||||
110
t/group_hosts_by.pl
Normal file
110
t/group_hosts_by.pl
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
use Test::More;
|
||||||
|
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
|
||||||
|
eval { require 'ddclient'; } or BAIL_OUT($@);
|
||||||
|
eval { require Data::Dumper; } or skip($@, 1);
|
||||||
|
Data::Dumper->import();
|
||||||
|
|
||||||
|
my $h1 = 'h1';
|
||||||
|
my $h2 = 'h2';
|
||||||
|
my $h3 = 'h3';
|
||||||
|
|
||||||
|
$ddclient::config{$h1} = {
|
||||||
|
common => 'common',
|
||||||
|
h1h2 => 'h1 and h2',
|
||||||
|
unique => 'h1',
|
||||||
|
falsy => 0,
|
||||||
|
maybeunset => 'unique',
|
||||||
|
};
|
||||||
|
$ddclient::config{$h2} = {
|
||||||
|
common => 'common',
|
||||||
|
h1h2 => 'h1 and h2',
|
||||||
|
unique => 'h2',
|
||||||
|
falsy => '',
|
||||||
|
maybeunset => undef, # should not be grouped with unset
|
||||||
|
};
|
||||||
|
$ddclient::config{$h3} = {
|
||||||
|
common => 'common',
|
||||||
|
h1h2 => 'unique',
|
||||||
|
unique => 'h3',
|
||||||
|
falsy => undef,
|
||||||
|
# maybeunset is intentionally not set
|
||||||
|
};
|
||||||
|
|
||||||
|
my @test_cases = (
|
||||||
|
{
|
||||||
|
desc => 'empty attribute set yields single group with all hosts',
|
||||||
|
groupby => [qw()],
|
||||||
|
want => [{cfg => {}, hosts => [$h1, $h2, $h3]}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'common attribute yields single group with all hosts',
|
||||||
|
groupby => [qw(common)],
|
||||||
|
want => [{cfg => {common => 'common'}, hosts => [$h1, $h2, $h3]}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'subset share a value',
|
||||||
|
groupby => [qw(h1h2)],
|
||||||
|
want => [
|
||||||
|
{cfg => {h1h2 => 'h1 and h2'}, hosts => [$h1, $h2]},
|
||||||
|
{cfg => {h1h2 => 'unique'}, hosts => [$h3]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'all unique',
|
||||||
|
groupby => [qw(unique)],
|
||||||
|
want => [
|
||||||
|
{cfg => {unique => 'h1'}, hosts => [$h1]},
|
||||||
|
{cfg => {unique => 'h2'}, hosts => [$h2]},
|
||||||
|
{cfg => {unique => 'h3'}, hosts => [$h3]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'combination',
|
||||||
|
groupby => [qw(common h1h2)],
|
||||||
|
want => [
|
||||||
|
{cfg => {common => 'common', h1h2 => 'h1 and h2'}, hosts => [$h1, $h2]},
|
||||||
|
{cfg => {common => 'common', h1h2 => 'unique'}, hosts => [$h3]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'falsy values',
|
||||||
|
groupby => [qw(falsy)],
|
||||||
|
want => [
|
||||||
|
{cfg => {falsy => 0}, hosts => [$h1]},
|
||||||
|
{cfg => {falsy => ''}, hosts => [$h2]},
|
||||||
|
{cfg => {falsy => undef}, hosts => [$h3]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'set, unset, undef',
|
||||||
|
groupby => [qw(maybeunset)],
|
||||||
|
want => [
|
||||||
|
{cfg => {maybeunset => 'unique'}, hosts => [$h1]},
|
||||||
|
{cfg => {maybeunset => undef}, hosts => [$h2]},
|
||||||
|
{cfg => {}, hosts => [$h3]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'missing attribute',
|
||||||
|
groupby => [qw(thisdoesnotexist)],
|
||||||
|
want => [{cfg => {}, hosts => [$h1, $h2, $h3]}],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
for my $tc (@test_cases) {
|
||||||
|
my @got = ddclient::group_hosts_by([$h1, $h2, $h3], @{$tc->{groupby}});
|
||||||
|
# @got is used as a set of sets. Sort everything to make comparison easier.
|
||||||
|
$_->{hosts} = [sort(@{$_->{hosts}})] for @got;
|
||||||
|
@got = sort({
|
||||||
|
for (my $i = 0; $i < @{$a->{hosts}} && $i < @{$b->{hosts}}; ++$i) {
|
||||||
|
my $x = $a->{hosts}[$i] cmp $b->{hosts}[$i];
|
||||||
|
return $x if $x != 0;
|
||||||
|
}
|
||||||
|
return @{$a->{hosts}} <=> @{$b->{hosts}};
|
||||||
|
} @got);
|
||||||
|
is_deeply(\@got, $tc->{want}, $tc->{desc})
|
||||||
|
or diag(Data::Dumper->new([\@got, $tc->{want}],
|
||||||
|
[qw(got want)])->Sortkeys(1)->Useqq(1)->Dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing();
|
||||||
74
t/header_ok.pl
Normal file
74
t/header_ok.pl
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
use Test::More;
|
||||||
|
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
|
||||||
|
eval { require 'ddclient'; } or BAIL_OUT($@);
|
||||||
|
my $have_mock = eval { require Test::MockModule; };
|
||||||
|
|
||||||
|
my $failmsg;
|
||||||
|
my $module;
|
||||||
|
if ($have_mock) {
|
||||||
|
$module = Test::MockModule->new('ddclient');
|
||||||
|
# Note: 'mock' is used instead of 'redefine' because 'redefine' is not available in the versions
|
||||||
|
# of Test::MockModule distributed with old Debian and Ubuntu releases.
|
||||||
|
$module->mock('failed', sub { $failmsg //= ''; $failmsg .= sprintf(shift, @_) . "\n"; });
|
||||||
|
}
|
||||||
|
|
||||||
|
my @test_cases = (
|
||||||
|
{
|
||||||
|
desc => 'malformed not OK',
|
||||||
|
input => 'malformed',
|
||||||
|
want => 0,
|
||||||
|
wantmsg => qr/unexpected/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'HTTP/1.1 200 OK',
|
||||||
|
input => 'HTTP/1.1 200 OK',
|
||||||
|
want => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'HTTP/2 200 OK',
|
||||||
|
input => 'HTTP/2 200 OK',
|
||||||
|
want => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'HTTP/3 200 OK',
|
||||||
|
input => 'HTTP/3 200 OK',
|
||||||
|
want => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => '401 not OK, fallback message',
|
||||||
|
input => 'HTTP/1.1 401 ',
|
||||||
|
want => 0,
|
||||||
|
wantmsg => qr/authentication failed/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => '403 not OK, fallback message',
|
||||||
|
input => 'HTTP/1.1 403 ',
|
||||||
|
want => 0,
|
||||||
|
wantmsg => qr/not authorized/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'other 4xx not OK',
|
||||||
|
input => 'HTTP/1.1 456 bad',
|
||||||
|
want => 0,
|
||||||
|
wantmsg => qr/bad/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc => 'only first line is logged on error',
|
||||||
|
input => "HTTP/1.1 404 not found\n\nbody",
|
||||||
|
want => 0,
|
||||||
|
wantmsg => qr/(?!body)/,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
for my $tc (@test_cases) {
|
||||||
|
subtest $tc->{desc} => sub {
|
||||||
|
$failmsg = '';
|
||||||
|
is(ddclient::header_ok('host', $tc->{input}), $tc->{want}, 'return value matches');
|
||||||
|
SKIP: {
|
||||||
|
skip('Test::MockModule not available') if !$have_mock;
|
||||||
|
like($failmsg, $tc->{wantmsg} // qr/^$/, 'fail message matches');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing();
|
||||||
51
t/interval_expired.pl
Normal file
51
t/interval_expired.pl
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
use Test::More;
|
||||||
|
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
|
||||||
|
eval { require 'ddclient'; } or BAIL_OUT($@);
|
||||||
|
|
||||||
|
my $h = 't/interval_expired.pl';
|
||||||
|
|
||||||
|
my $default_now = 1000000000;
|
||||||
|
|
||||||
|
my @test_cases = (
|
||||||
|
{
|
||||||
|
interval => 'inf',
|
||||||
|
want => 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
now => 'inf',
|
||||||
|
interval => 'inf',
|
||||||
|
want => 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cache => '-inf',
|
||||||
|
interval => 'inf',
|
||||||
|
want => 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cache => undef, # Falsy cache value.
|
||||||
|
interval => 'inf',
|
||||||
|
want => 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
now => 0,
|
||||||
|
cache => 0, # Different kind of falsy cache value.
|
||||||
|
interval => 'inf',
|
||||||
|
want => 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
for my $tc (@test_cases) {
|
||||||
|
$tc->{now} //= $default_now;
|
||||||
|
# For convenience, $tc->{cache} is an offset from $tc->{now}, not an absolute time..
|
||||||
|
my $cachetime = $tc->{now} + $tc->{cache} if defined($tc->{cache});
|
||||||
|
$ddclient::config{$h} = {'interval' => $tc->{interval}};
|
||||||
|
%ddclient::config if 0; # suppress spurious warning "Name used only once: possible typo"
|
||||||
|
$ddclient::cache{$h} = {'cached-time' => $cachetime} if defined($cachetime);
|
||||||
|
%ddclient::cache if 0; # suppress spurious warning "Name used only once: possible typo"
|
||||||
|
$ddclient::now = $tc->{now};
|
||||||
|
$ddclient::now if 0; # suppress spurious warning "Name used only once: possible typo"
|
||||||
|
my $desc = "now=$tc->{now}, cache=${\($cachetime // 'undef')}, interval=$tc->{interval}";
|
||||||
|
is(ddclient::interval_expired($h, 'cached-time', 'interval'), $tc->{want}, $desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
done_testing();
|
||||||
32
t/variable_defaults.pl
Normal file
32
t/variable_defaults.pl
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
use Test::More;
|
||||||
|
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
|
||||||
|
eval { require 'ddclient'; } or BAIL_OUT($@);
|
||||||
|
|
||||||
|
my %variable_collections = (
|
||||||
|
map({ ($_ => $ddclient::variables{$_}) } grep($_ ne 'merged', keys(%ddclient::variables))),
|
||||||
|
map({ ("protocol=$_" => $ddclient::protocols{$_}{variables}); } keys(%ddclient::protocols)),
|
||||||
|
);
|
||||||
|
my %seen;
|
||||||
|
my @test_cases = (
|
||||||
|
map({
|
||||||
|
my $vcn = $_;
|
||||||
|
my $vc = $variable_collections{$_};
|
||||||
|
map({
|
||||||
|
my $def = $vc->{$_};
|
||||||
|
my $seen = exists($seen{$def});
|
||||||
|
$seen{$def} = undef;
|
||||||
|
({desc => "$vcn $_", def => $vc->{$_}}) x !$seen;
|
||||||
|
} sort(keys(%$vc)));
|
||||||
|
} sort(keys(%variable_collections))),
|
||||||
|
);
|
||||||
|
for my $tc (@test_cases) {
|
||||||
|
if ($tc->{def}{required}) {
|
||||||
|
is($tc->{def}{default}, undef, "'$tc->{desc}' (required) has no default");
|
||||||
|
} else {
|
||||||
|
my $norm;
|
||||||
|
my $valid = eval { $norm = ddclient::check_value($tc->{def}{default}, $tc->{def}); 1; };
|
||||||
|
ok($valid, "'$tc->{desc}' (optional) has a valid default");
|
||||||
|
is($norm, $tc->{def}{default}, "'$tc->{desc}' default normalizes to itself") if $valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done_testing();
|
||||||
|
|
@ -35,7 +35,7 @@ my @test_cases = (
|
||||||
|
|
||||||
for my $tc (@test_cases) {
|
for my $tc (@test_cases) {
|
||||||
$warning = undef;
|
$warning = undef;
|
||||||
ddclient::write_cache($tc->{f});
|
ddclient::write_recap($tc->{f});
|
||||||
subtest $tc->{name} => sub {
|
subtest $tc->{name} => sub {
|
||||||
if (defined($tc->{warning_regex})) {
|
if (defined($tc->{warning_regex})) {
|
||||||
like($warning, $tc->{warning_regex}, "expected warning message");
|
like($warning, $tc->{warning_regex}, "expected warning message");
|
||||||
Loading…
Reference in a new issue