2019-03-11 16:56:48 +00:00
package internal
2019-03-01 18:09:00 +00:00
import (
2023-05-05 07:49:38 +00:00
"context"
2019-03-01 18:09:00 +00:00
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
2023-05-05 07:49:38 +00:00
func setupTest ( t * testing . T , subAuthID string , handler http . HandlerFunc ) * Client {
t . Helper ( )
server := httptest . NewServer ( handler )
t . Cleanup ( server . Close )
client , err := NewClient ( "myAuthID" , subAuthID , "myAuthPassword" )
require . NoError ( t , err )
client . BaseURL , _ = url . Parse ( server . URL )
client . HTTPClient = server . Client ( )
return client
}
2021-02-28 12:25:15 +00:00
func handlerMock ( method string , jsonData [ ] byte ) http . HandlerFunc {
return func ( rw http . ResponseWriter , req * http . Request ) {
2019-03-01 18:09:00 +00:00
if req . Method != method {
http . Error ( rw , "Incorrect method used" , http . StatusBadRequest )
return
}
_ , err := rw . Write ( jsonData )
if err != nil {
http . Error ( rw , err . Error ( ) , http . StatusInternalServerError )
return
}
2021-02-28 12:25:15 +00:00
}
}
func TestNewClient ( t * testing . T ) {
testCases := [ ] struct {
desc string
authID string
subAuthID string
authPassword string
expected string
} {
{
desc : "all provided" ,
authID : "1000" ,
subAuthID : "1111" ,
authPassword : "no-secret" ,
} ,
{
desc : "missing authID & subAuthID" ,
authID : "" ,
subAuthID : "" ,
authPassword : "no-secret" ,
expected : "credentials missing: authID or subAuthID" ,
} ,
{
desc : "missing authID & subAuthID" ,
authID : "" ,
subAuthID : "present" ,
authPassword : "" ,
expected : "credentials missing: authPassword" ,
} ,
}
for _ , test := range testCases {
t . Run ( test . desc , func ( t * testing . T ) {
client , err := NewClient ( test . authID , test . subAuthID , test . authPassword )
if test . expected != "" {
assert . Nil ( t , client )
require . EqualError ( t , err , test . expected )
} else {
assert . NotNil ( t , client )
require . NoError ( t , err )
}
} )
}
2019-03-01 18:09:00 +00:00
}
2021-02-28 12:25:15 +00:00
func TestClient_GetZone ( t * testing . T ) {
type expected struct {
zone * Zone
errorMsg string
2019-03-01 18:09:00 +00:00
}
2021-02-28 12:25:15 +00:00
2019-03-01 18:09:00 +00:00
testCases := [ ] struct {
desc string
authFQDN string
2021-02-28 12:25:15 +00:00
apiResponse string
expected
2019-03-01 18:09:00 +00:00
} {
{
desc : "zone found" ,
authFQDN : "_acme-challenge.foo.com." ,
2021-02-28 12:25:15 +00:00
apiResponse : ` { "name": "foo.com", "type": "master", "zone": "zone", "status": "1"} ` ,
expected : expected {
2019-03-01 18:09:00 +00:00
zone : & Zone {
Name : "foo.com" ,
Type : "master" ,
Zone : "zone" ,
Status : "1" ,
} ,
} ,
} ,
{
desc : "zone not found" ,
authFQDN : "_acme-challenge.foo.com." ,
2021-02-28 12:25:15 +00:00
apiResponse : ` ` ,
expected : expected {
errorMsg : "zone foo.com not found for authFQDN _acme-challenge.foo.com." ,
} ,
} ,
{
desc : "invalid json response" ,
authFQDN : "_acme-challenge.foo.com." ,
apiResponse : ` [ { }] ` ,
expected : expected {
2023-05-05 07:49:38 +00:00
errorMsg : "unable to unmarshal response: [status code: 200] body: [{}] error: json: cannot unmarshal array into Go value of type internal.Zone" ,
2021-02-28 12:25:15 +00:00
} ,
2019-03-01 18:09:00 +00:00
} ,
}
for _ , test := range testCases {
t . Run ( test . desc , func ( t * testing . T ) {
2023-05-05 07:49:38 +00:00
client := setupTest ( t , "" , handlerMock ( http . MethodGet , [ ] byte ( test . apiResponse ) ) )
2021-02-28 12:25:15 +00:00
2023-05-05 07:49:38 +00:00
zone , err := client . GetZone ( context . Background ( ) , test . authFQDN )
2019-03-01 18:09:00 +00:00
2021-02-28 12:25:15 +00:00
if test . expected . errorMsg != "" {
require . EqualError ( t , err , test . expected . errorMsg )
2019-03-01 18:09:00 +00:00
} else {
require . NoError ( t , err )
assert . Equal ( t , test . expected . zone , zone )
}
} )
}
}
2021-02-28 12:25:15 +00:00
func TestClient_FindTxtRecord ( t * testing . T ) {
type expected struct {
2019-03-01 18:09:00 +00:00
txtRecord * TXTRecord
2021-02-28 12:25:15 +00:00
errorMsg string
2019-03-01 18:09:00 +00:00
}
testCases := [ ] struct {
desc string
authFQDN string
zoneName string
2021-02-28 12:25:15 +00:00
apiResponse string
expected
2019-03-01 18:09:00 +00:00
} {
{
2019-04-17 20:41:51 +00:00
desc : "record found" ,
authFQDN : "_acme-challenge.foo.com." ,
zoneName : "foo.com" ,
2021-02-28 12:25:15 +00:00
apiResponse : ` {
2019-04-17 20:41:51 +00:00
"5769228" : {
"id" : "5769228" ,
"type" : "TXT" ,
"host" : "_acme-challenge" ,
"record" : "txtTXTtxtTXTtxtTXTtxtTXT" ,
"failover" : "0" ,
"ttl" : "3600" ,
"status" : 1
} ,
"181805209" : {
"id" : "181805209" ,
"type" : "TXT" ,
"host" : "_github-challenge" ,
"record" : "b66b8324b5" ,
"failover" : "0" ,
"ttl" : "300" ,
"status" : 1
}
2021-02-28 12:25:15 +00:00
} ` ,
expected : expected {
2019-03-01 18:09:00 +00:00
txtRecord : & TXTRecord {
2019-04-17 20:41:51 +00:00
ID : 5769228 ,
2019-03-01 18:09:00 +00:00
Type : "TXT" ,
Host : "_acme-challenge" ,
Record : "txtTXTtxtTXTtxtTXTtxtTXT" ,
2019-04-17 20:41:51 +00:00
Failover : 0 ,
TTL : 3600 ,
2019-03-01 18:09:00 +00:00
Status : 1 ,
} ,
} ,
} ,
{
2021-02-28 12:25:15 +00:00
desc : "no record found" ,
authFQDN : "_acme-challenge.foo.com." ,
zoneName : "foo.com" ,
apiResponse : ` {
"5769228" : {
"id" : "5769228" ,
"type" : "TXT" ,
"host" : "_other-challenge" ,
"record" : "txtTXTtxtTXTtxtTXTtxtTXT" ,
"failover" : "0" ,
"ttl" : "3600" ,
"status" : 1
} ,
"181805209" : {
"id" : "181805209" ,
"type" : "TXT" ,
"host" : "_github-challenge" ,
"record" : "b66b8324b5" ,
"failover" : "0" ,
"ttl" : "300" ,
"status" : 1
}
} ` ,
} ,
{
desc : "zero records" ,
2022-12-04 15:11:27 +00:00
authFQDN : "_acme-challenge.example.com." ,
zoneName : "example.com" ,
2021-02-28 12:25:15 +00:00
apiResponse : ` [] ` ,
} ,
{
desc : "invalid json response" ,
2022-12-04 15:11:27 +00:00
authFQDN : "_acme-challenge.example.com." ,
zoneName : "example.com" ,
2021-02-28 12:25:15 +00:00
apiResponse : ` [ { }] ` ,
expected : expected {
2023-05-05 07:49:38 +00:00
errorMsg : "unable to unmarshal response: [status code: 200] body: [{}] error: json: cannot unmarshal array into Go value of type map[string]internal.TXTRecord" ,
2021-02-28 12:25:15 +00:00
} ,
2019-03-01 18:09:00 +00:00
} ,
}
for _ , test := range testCases {
t . Run ( test . desc , func ( t * testing . T ) {
2023-05-05 07:49:38 +00:00
client := setupTest ( t , "" , handlerMock ( http . MethodGet , [ ] byte ( test . apiResponse ) ) )
2019-03-01 18:09:00 +00:00
2023-05-05 07:49:38 +00:00
txtRecord , err := client . FindTxtRecord ( context . Background ( ) , test . zoneName , test . authFQDN )
2019-03-01 18:09:00 +00:00
2021-02-28 12:25:15 +00:00
if test . expected . errorMsg != "" {
require . EqualError ( t , err , test . expected . errorMsg )
2019-03-01 18:09:00 +00:00
} else {
require . NoError ( t , err )
assert . Equal ( t , test . expected . txtRecord , txtRecord )
}
} )
}
}
2021-02-28 12:25:15 +00:00
func TestClient_ListTxtRecord ( t * testing . T ) {
2019-04-17 20:41:51 +00:00
type expected struct {
2021-02-28 12:25:15 +00:00
txtRecords [ ] TXTRecord
errorMsg string
}
testCases := [ ] struct {
desc string
authFQDN string
zoneName string
apiResponse string
expected
} {
{
desc : "record found" ,
authFQDN : "_acme-challenge.foo.com." ,
zoneName : "foo.com" ,
apiResponse : ` {
"5769228" : {
"id" : "5769228" ,
"type" : "TXT" ,
"host" : "_acme-challenge" ,
"record" : "txtTXTtxtTXTtxtTXTtxtTXT" ,
"failover" : "0" ,
"ttl" : "3600" ,
"status" : 1
} ,
"181805209" : {
"id" : "181805209" ,
"type" : "TXT" ,
"host" : "_github-challenge" ,
"record" : "b66b8324b5" ,
"failover" : "0" ,
"ttl" : "300" ,
"status" : 1
}
} ` ,
expected : expected {
txtRecords : [ ] TXTRecord {
{
ID : 5769228 ,
Type : "TXT" ,
Host : "_acme-challenge" ,
Record : "txtTXTtxtTXTtxtTXTtxtTXT" ,
Failover : 0 ,
TTL : 3600 ,
Status : 1 ,
} ,
} ,
} ,
} ,
{
desc : "no record found" ,
authFQDN : "_acme-challenge.foo.com." ,
zoneName : "foo.com" ,
apiResponse : ` {
"5769228" : {
"id" : "5769228" ,
"type" : "TXT" ,
"host" : "_other-challenge" ,
"record" : "txtTXTtxtTXTtxtTXTtxtTXT" ,
"failover" : "0" ,
"ttl" : "3600" ,
"status" : 1
} ,
"181805209" : {
"id" : "181805209" ,
"type" : "TXT" ,
"host" : "_github-challenge" ,
"record" : "b66b8324b5" ,
"failover" : "0" ,
"ttl" : "300" ,
"status" : 1
}
} ` ,
} ,
{
desc : "zero records" ,
2022-12-04 15:11:27 +00:00
authFQDN : "_acme-challenge.example.com." ,
zoneName : "example.com" ,
2021-02-28 12:25:15 +00:00
apiResponse : ` [] ` ,
} ,
{
desc : "invalid json response" ,
2022-12-04 15:11:27 +00:00
authFQDN : "_acme-challenge.example.com." ,
zoneName : "example.com" ,
2021-02-28 12:25:15 +00:00
apiResponse : ` [ { }] ` ,
expected : expected {
2023-05-05 07:49:38 +00:00
errorMsg : "unable to unmarshal response: [status code: 200] body: [{}] error: json: cannot unmarshal array into Go value of type map[string]internal.TXTRecord" ,
2021-02-28 12:25:15 +00:00
} ,
} ,
}
for _ , test := range testCases {
t . Run ( test . desc , func ( t * testing . T ) {
2023-05-05 07:49:38 +00:00
client := setupTest ( t , "" , handlerMock ( http . MethodGet , [ ] byte ( test . apiResponse ) ) )
2021-02-28 12:25:15 +00:00
2023-05-05 07:49:38 +00:00
txtRecords , err := client . ListTxtRecords ( context . Background ( ) , test . zoneName , test . authFQDN )
2021-02-28 12:25:15 +00:00
if test . expected . errorMsg != "" {
require . EqualError ( t , err , test . expected . errorMsg )
} else {
require . NoError ( t , err )
assert . Equal ( t , test . expected . txtRecords , txtRecords )
}
} )
}
}
func TestClient_AddTxtRecord ( t * testing . T ) {
type expected struct {
query string
errorMsg string
2019-04-17 20:41:51 +00:00
}
2019-03-01 18:09:00 +00:00
testCases := [ ] struct {
2019-04-17 20:41:51 +00:00
desc string
2020-03-25 08:34:23 +00:00
authID string
subAuthID string
2021-02-28 12:25:15 +00:00
zoneName string
2019-04-17 20:41:51 +00:00
authFQDN string
value string
ttl int
2021-02-28 12:25:15 +00:00
apiResponse string
expected
2019-03-01 18:09:00 +00:00
} {
{
2021-02-28 12:25:15 +00:00
desc : "sub-zone" ,
authID : "myAuthID" ,
2024-02-24 19:49:40 +00:00
zoneName : "example.com" ,
authFQDN : "_acme-challenge.foo.example.com." ,
2019-04-17 20:41:51 +00:00
value : "txtTXTtxtTXTtxtTXTtxtTXT" ,
ttl : 60 ,
2021-02-28 12:25:15 +00:00
apiResponse : ` { "status":"Success","statusDescription":"The record was added successfully."} ` ,
2019-04-17 20:41:51 +00:00
expected : expected {
2024-02-24 19:49:40 +00:00
query : ` auth-id=myAuthID&auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge.foo&record=txtTXTtxtTXTtxtTXTtxtTXT&record-type=TXT&ttl=60 ` ,
2019-04-17 20:41:51 +00:00
} ,
2019-03-01 18:09:00 +00:00
} ,
{
2021-02-28 12:25:15 +00:00
desc : "main zone (authID)" ,
authID : "myAuthID" ,
2024-02-24 19:49:40 +00:00
zoneName : "example.com" ,
authFQDN : "_acme-challenge.example.com." ,
2019-04-17 20:41:51 +00:00
value : "TXTtxtTXTtxtTXTtxtTXTtxt" ,
ttl : 60 ,
2021-02-28 12:25:15 +00:00
apiResponse : ` { "status":"Success","statusDescription":"The record was added successfully."} ` ,
2019-04-17 20:41:51 +00:00
expected : expected {
2024-02-24 19:49:40 +00:00
query : ` auth-id=myAuthID&auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=60 ` ,
2019-04-17 20:41:51 +00:00
} ,
} ,
{
2021-02-28 12:25:15 +00:00
desc : "main zone (subAuthID)" ,
subAuthID : "mySubAuthID" ,
2024-02-24 19:49:40 +00:00
zoneName : "example.com" ,
authFQDN : "_acme-challenge.example.com." ,
2020-03-25 08:34:23 +00:00
value : "TXTtxtTXTtxtTXTtxtTXTtxt" ,
ttl : 60 ,
2021-02-28 12:25:15 +00:00
apiResponse : ` { "status":"Success","statusDescription":"The record was added successfully."} ` ,
2020-03-25 08:34:23 +00:00
expected : expected {
2024-02-24 19:49:40 +00:00
query : ` auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&sub-auth-id=mySubAuthID&ttl=60 ` ,
2020-03-25 08:34:23 +00:00
} ,
} ,
{
2021-02-28 12:25:15 +00:00
desc : "invalid status" ,
authID : "myAuthID" ,
2024-02-24 19:49:40 +00:00
zoneName : "example.com" ,
authFQDN : "_acme-challenge.example.com." ,
2021-02-28 12:25:15 +00:00
value : "TXTtxtTXTtxtTXTtxtTXTtxt" ,
ttl : 120 ,
apiResponse : ` { "status":"Failed","statusDescription":"Invalid TTL. Choose from the list of the values we support."} ` ,
expected : expected {
2024-02-24 19:49:40 +00:00
query : ` auth-id=myAuthID&auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=300 ` ,
2021-02-28 12:25:15 +00:00
errorMsg : "failed to add TXT record: Failed Invalid TTL. Choose from the list of the values we support." ,
2019-04-17 20:41:51 +00:00
} ,
2021-02-28 12:25:15 +00:00
} ,
{
desc : "invalid json response" ,
authID : "myAuthID" ,
2024-02-24 19:49:40 +00:00
zoneName : "example.com" ,
authFQDN : "_acme-challenge.example.com." ,
2019-04-17 20:41:51 +00:00
value : "TXTtxtTXTtxtTXTtxtTXTtxt" ,
ttl : 120 ,
2021-02-28 12:25:15 +00:00
apiResponse : ` [ { }] ` ,
2019-04-17 20:41:51 +00:00
expected : expected {
2024-02-24 19:49:40 +00:00
query : ` auth-id=myAuthID&auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=300 ` ,
2023-05-05 07:49:38 +00:00
errorMsg : "unable to unmarshal response: [status code: 200] body: [{}] error: json: cannot unmarshal array into Go value of type internal.apiResponse" ,
2019-04-17 20:41:51 +00:00
} ,
2019-03-01 18:09:00 +00:00
} ,
}
for _ , test := range testCases {
t . Run ( test . desc , func ( t * testing . T ) {
2023-05-05 07:49:38 +00:00
client := setupTest ( t , test . subAuthID , func ( rw http . ResponseWriter , req * http . Request ) {
2021-02-28 12:25:15 +00:00
if test . expected . query != req . URL . RawQuery {
msg := fmt . Sprintf ( "got: %s, want: %s" , test . expected . query , req . URL . RawQuery )
http . Error ( rw , msg , http . StatusBadRequest )
return
}
2019-03-01 18:09:00 +00:00
2021-02-28 12:25:15 +00:00
handlerMock ( http . MethodPost , [ ] byte ( test . apiResponse ) ) ( rw , req )
2023-05-05 07:49:38 +00:00
} )
2019-03-01 18:09:00 +00:00
2023-05-05 07:49:38 +00:00
err := client . AddTxtRecord ( context . Background ( ) , test . zoneName , test . authFQDN , test . value , test . ttl )
2019-04-17 20:41:51 +00:00
2021-02-28 12:25:15 +00:00
if test . expected . errorMsg != "" {
require . EqualError ( t , err , test . expected . errorMsg )
2019-04-17 20:41:51 +00:00
} else {
require . NoError ( t , err )
}
2019-03-01 18:09:00 +00:00
} )
}
}
2021-02-28 12:25:15 +00:00
func TestClient_RemoveTxtRecord ( t * testing . T ) {
type expected struct {
query string
errorMsg string
}
testCases := [ ] struct {
desc string
id int
zoneName string
apiResponse string
expected
} {
{
desc : "record found" ,
id : 5769228 ,
zoneName : "foo.com" ,
apiResponse : ` { "status": "Success", "statusDescription": "The record was deleted successfully." } ` ,
expected : expected {
query : ` auth-id=myAuthID&auth-password=myAuthPassword&domain-name=foo.com&record-id=5769228 ` ,
} ,
} ,
{
desc : "record not found" ,
id : 5769000 ,
zoneName : "foo.com" ,
apiResponse : ` { "status": "Failed", "statusDescription": "Invalid record-id param." } ` ,
expected : expected {
query : ` auth-id=myAuthID&auth-password=myAuthPassword&domain-name=foo.com&record-id=5769000 ` ,
errorMsg : "failed to remove TXT record: Failed Invalid record-id param." ,
} ,
} ,
{
desc : "invalid json response" ,
id : 44 ,
zoneName : "foo-plus.com" ,
apiResponse : ` [ { }] ` ,
expected : expected {
query : ` auth-id=myAuthID&auth-password=myAuthPassword&domain-name=foo-plus.com&record-id=44 ` ,
2023-05-05 07:49:38 +00:00
errorMsg : "unable to unmarshal response: [status code: 200] body: [{}] error: json: cannot unmarshal array into Go value of type internal.apiResponse" ,
2021-02-28 12:25:15 +00:00
} ,
} ,
}
for _ , test := range testCases {
t . Run ( test . desc , func ( t * testing . T ) {
server := httptest . NewServer ( http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
if test . expected . query != req . URL . RawQuery {
msg := fmt . Sprintf ( "got: %s, want: %s" , test . expected . query , req . URL . RawQuery )
http . Error ( rw , msg , http . StatusBadRequest )
return
}
handlerMock ( http . MethodPost , [ ] byte ( test . apiResponse ) ) ( rw , req )
} ) )
t . Cleanup ( server . Close )
client , err := NewClient ( "myAuthID" , "" , "myAuthPassword" )
require . NoError ( t , err )
client . BaseURL , _ = url . Parse ( server . URL )
2023-05-05 07:49:38 +00:00
err = client . RemoveTxtRecord ( context . Background ( ) , test . id , test . zoneName )
2021-02-28 12:25:15 +00:00
if test . expected . errorMsg != "" {
require . EqualError ( t , err , test . expected . errorMsg )
} else {
require . NoError ( t , err )
}
} )
}
}
func TestClient_GetUpdateStatus ( t * testing . T ) {
type expected struct {
progress * SyncProgress
errorMsg string
}
testCases := [ ] struct {
desc string
authFQDN string
zoneName string
apiResponse string
expected
} {
{
desc : "50% sync" ,
authFQDN : "_acme-challenge.foo.com." ,
zoneName : "foo.com" ,
apiResponse : ` [
{ "server" : "ns101.foo.com." , "ip4" : "10.11.12.13" , "ip6" : "2a00:2a00:2a00:9::5" , "updated" : true } ,
{ "server" : "ns102.foo.com." , "ip4" : "10.14.16.17" , "ip6" : "2100:2100:2100:3::1" , "updated" : false }
] ` ,
expected : expected { progress : & SyncProgress { Updated : 1 , Total : 2 } } ,
} ,
{
desc : "100% sync" ,
authFQDN : "_acme-challenge.foo.com." ,
zoneName : "foo.com" ,
apiResponse : ` [
{ "server" : "ns101.foo.com." , "ip4" : "10.11.12.13" , "ip6" : "2a00:2a00:2a00:9::5" , "updated" : true } ,
{ "server" : "ns102.foo.com." , "ip4" : "10.14.16.17" , "ip6" : "2100:2100:2100:3::1" , "updated" : true }
] ` ,
expected : expected { progress : & SyncProgress { Complete : true , Updated : 2 , Total : 2 } } ,
} ,
{
desc : "record not found" ,
authFQDN : "_acme-challenge.foo.com." ,
zoneName : "test-zone" ,
apiResponse : ` [] ` ,
expected : expected { errorMsg : "no nameservers records returned" } ,
} ,
{
desc : "invalid json response" ,
authFQDN : "_acme-challenge.foo.com." ,
zoneName : "test-zone" ,
apiResponse : ` [x] ` ,
2023-05-05 07:49:38 +00:00
expected : expected { errorMsg : "unable to unmarshal response: [status code: 200] body: [x] error: invalid character 'x' looking for beginning of value" } ,
2021-02-28 12:25:15 +00:00
} ,
}
for _ , test := range testCases {
t . Run ( test . desc , func ( t * testing . T ) {
server := httptest . NewServer ( handlerMock ( http . MethodGet , [ ] byte ( test . apiResponse ) ) )
t . Cleanup ( server . Close )
client , err := NewClient ( "myAuthID" , "" , "myAuthPassword" )
require . NoError ( t , err )
client . BaseURL , _ = url . Parse ( server . URL )
2023-05-05 07:49:38 +00:00
syncProgress , err := client . GetUpdateStatus ( context . Background ( ) , test . zoneName )
2021-02-28 12:25:15 +00:00
if test . expected . errorMsg != "" {
require . EqualError ( t , err , test . expected . errorMsg )
} else {
require . NoError ( t , err )
}
assert . Equal ( t , test . expected . progress , syncProgress )
} )
}
}