diff --git a/README.md b/README.md index 875ea47..5418f24 100644 --- a/README.md +++ b/README.md @@ -59,5 +59,3 @@ This project is a work-in-progress, see the following table for supported featur ||Delete|DELETE|✔️| >**Note** Features marked with a * are additional to what the API provides - ->**Note** At the time of writing, `UpdateDNSRecord` currently does not work as described in the API docs. See https://github.com/neatnik/omg.lol/issues/584. `ReplaceDNSRecord` can be used instead. diff --git a/omglol/account.go b/omglol/account.go index ed7699e..cd697f0 100644 --- a/omglol/account.go +++ b/omglol/account.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" ) // Get information about your account. See https://api.omg.lol/#token-get-account-retrieve-account-information @@ -118,23 +119,28 @@ func (c *Client) GetActiveSessions() (*[]ActiveSession, error) { return nil, err } + var Sessions []ActiveSession + body, err := c.doRequest(req) if err != nil { + if strings.Contains(err.Error(), "status: 404") { + return &Sessions, nil + } return nil, err } - type sessionsResponse struct { - Request request `json:"request"` - Sessions []ActiveSession `json:"response"` - } - - var r sessionsResponse + var r apiResponse if err := json.Unmarshal(body, &r); err != nil { fmt.Printf("Error unmarshalling response: %v\n", err) return nil, err + } + + if err := json.Unmarshal(r.Response, &Sessions); err != nil { + fmt.Printf("Error unmarshalling sessions: %v\n", err) + return nil, err } - return &r.Sessions, nil + return &Sessions, nil } // Delete a session. See https://api.omg.lol/#token-delete-account-remove-a-session diff --git a/omglol/account_test.go b/omglol/account_test.go index b97e907..6456c98 100644 --- a/omglol/account_test.go +++ b/omglol/account_test.go @@ -1,11 +1,13 @@ package omglol import ( + "net" "os" "testing" ) func TestGetAccountInfo(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -17,10 +19,33 @@ func TestGetAccountInfo(t *testing.T) { t.Errorf(err.Error()) } - t.Logf("%+v\n", *a) + if a != nil { + t.Logf("%+v\n", *a) + + if a.APIKey != c.Auth.ApiKey { + t.Error("Incorrect API key") + } + if a.Email != c.Auth.Email { + t.Errorf("Incorrect email: %s", a.Email) + } + if a.Settings.Communication != nil && !isOneOf(*a.Settings.Communication, []string{"email_ok", "email_not_ok"}) { + t.Errorf("Invalid communication setting: %s", *a.Settings.Communication) + } + if a.Settings.DateFormat != nil && !isOneOf(*a.Settings.DateFormat, []string{"iso_8601", "dmy", "mdy"}) { + t.Errorf("Invalid date format setting: %s", *a.Settings.DateFormat) + } + if a.Settings.Owner != testEmail { + t.Errorf("Settings.Owner %s did not match expected %s.", a.Settings.Owner, testEmail) + } + testTimestamps(t, a.Created.UnixEpochTime, a.Created.Iso8601Time, a.Created.Rfc2822Time, a.Created.RelativeTime) + } else { + t.Error("Account Info returned 'nil'.") + } + } func TestGetAddresses(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -32,10 +57,24 @@ func TestGetAddresses(t *testing.T) { t.Errorf(err.Error()) } - t.Logf("%+v\n", *a) + if a != nil { + t.Logf("%+v\n", *a) + + for _, addr := range *a { + if len(addr.Address) <= 0 { + t.Errorf("Invalid address") + } + testTimestamps(t, addr.Expiration.UnixEpochTime, addr.Expiration.Iso8601Time, addr.Expiration.Rfc2822Time, addr.Expiration.RelativeTime) + testTimestamps(t, addr.Registration.UnixEpochTime, addr.Registration.Iso8601Time, addr.Registration.Rfc2822Time, addr.Registration.RelativeTime) + } + } else { + t.Error("Addresses returned 'nil'.") + } + } func TestSetAccountName(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -48,6 +87,7 @@ func TestSetAccountName(t *testing.T) { } func TestGetAccountName(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -59,35 +99,63 @@ func TestGetAccountName(t *testing.T) { t.Errorf(err.Error()) } - t.Logf("%+v\n", *name) + if name != nil { + t.Logf("%+v\n", *name) - if *name != testName { - t.Errorf("Expected %s, got %s", testName, *name) + if *name != testName { + t.Errorf("Expected %s, got %s", testName, *name) + } + } else { + t.Error("Account name returned 'nil'.") } + } func TestGetActiveSessions(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { t.Errorf(err.Error()) } - a, err := c.GetActiveSessions() + s, err := c.GetActiveSessions() if err != nil { t.Errorf(err.Error()) } - t.Logf("%+v\n", a) + if s != nil { + t.Logf("Active Sessions: %+v", *s) + for _, x := range *s { + if len(x.SessionID) != 32 { + t.Errorf("Session ID %s is not expected length of 32, got length %d", x.SessionID, len(x.SessionID)) + } + if len(x.UserAgent) <= 0 { + t.Errorf("user_agent is empty") + } + if net.ParseIP(x.CreatedIP) == nil { + t.Errorf("Invalid IP address: %s", x.CreatedIP) + } + if x.CreatedOn == 0 { + t.Errorf("Created on timestamp is 0.") + } + if x.CreatedOn > x.ExpiresOn { + t.Errorf("Create date: %d, is after expire date: %d", x.CreatedOn, x.ExpiresOn) + } + } + } else { + t.Error("Active sessions returned 'nil'.") + } + } // This test cannot currently be run automatically -func TestDeleteActiveSession(t *testing.T) { - sessionID := os.Getenv("OMGLOL_DELETABLE_SESSION_ID") - - if sessionID == "" { +func TestDeleteActiveSession(t *testing.T) { + sessionID, exists := os.LookupEnv("OMGLOL_DELETABLE_SESSION_ID") + if !exists { t.Skip() } + sleep() c, err := NewClient(testEmail, testKey, testHostURL) @@ -101,6 +169,7 @@ func TestDeleteActiveSession(t *testing.T) { } func TestGetAccountSettings(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -112,10 +181,27 @@ func TestGetAccountSettings(t *testing.T) { t.Errorf(err.Error()) } - t.Logf("%+v\n", s) + if s != nil { + t.Logf("%+v\n", *s) + + if s.Communication != nil && !isOneOf(*s.Communication, []string{"email_ok", "email_not_ok"}) { + t.Errorf("Invalid communication setting: %s", *s.Communication) + } + if s.DateFormat != nil && !isOneOf(*s.DateFormat, []string{"iso_8601", "dmy", "mdy"}) { + t.Errorf("Invalid date format setting: %s", *s.DateFormat) + } + if s.Owner != testEmail { + t.Errorf("Settings.Owner %s did not match expected %s.", s.Owner, testEmail) + } + // s.WebEditor appears to be depricated + } else { + t.Errorf("Account Settings returned 'nil'.") + } + } func TestSetAccountSettings(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { diff --git a/omglol/address.go b/omglol/address.go index 32a6be5..d211e46 100644 --- a/omglol/address.go +++ b/omglol/address.go @@ -32,8 +32,8 @@ func (c *Client) GetAddressAvailability(address string) (*AddressAvailability, e return &a.Availability, nil } -// Get the expiration date for an address. See https://api.omg.lol/#noauth-get-address-retrieve-address-expiration -func (c *Client) GetAddressExpiration(address string) (*AddressExpiration, error) { +// Get the expiration data for an address. Returns `true` if expired, `false` if not. See https://api.omg.lol/#noauth-get-address-retrieve-address-expiration +func (c *Client) GetAddressExpiration(address string) (*bool, error) { req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/address/%s/expiration", c.HostURL, address), nil) if err != nil { return nil, err @@ -45,8 +45,11 @@ func (c *Client) GetAddressExpiration(address string) (*AddressExpiration, error } type expirationResponse struct { - Request request `json:"request"` - Expiration AddressExpiration `json:"response"` + Request request `json:"request"` + Response struct { + Message string `json:"message"` + Expired bool `json:"expired"` + } `json:"response"` } var e expirationResponse @@ -55,12 +58,12 @@ func (c *Client) GetAddressExpiration(address string) (*AddressExpiration, error return nil, err } - return &e.Expiration, nil + return &e.Response.Expired, nil } // Get comprehensive information about an address. See https://api.omg.lol/#token-get-address-retrieve-private-information-about-an-address func (c *Client) GetAddressInfo(address string) (*AddressInfo, error) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/address/%s/availability", c.HostURL, address), nil) + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/address/%s/info", c.HostURL, address), nil) if err != nil { return nil, err } diff --git a/omglol/address_test.go b/omglol/address_test.go index 5e5c79b..e6c974b 100644 --- a/omglol/address_test.go +++ b/omglol/address_test.go @@ -1,55 +1,132 @@ package omglol import ( + "strings" "testing" ) func TestGetAddressAvailability(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { t.Errorf(err.Error()) } - a, err := c.GetAddressAvailability("test") + // Test unavailable address + u, err := c.GetAddressAvailability(testOwnedDomain) if err != nil { t.Errorf(err.Error()) } - t.Logf("%+v\n", *a) + if u != nil { + t.Logf("%+v\n", *u) + + if u.Address != testOwnedDomain { + t.Errorf("Returned address: %s did not match expected address %s.", u.Address, testOwnedDomain) + } + if strings.ToLower(u.Availability) != "unavailable" { + t.Errorf("Returned availability: %s did not match expected 'unavailable'.", u.Availability) + } + if u.Available != false { + t.Errorf("Return available field was %t, expected false.", u.Available) + } + } else { + t.Error("Address Availability returned 'nil' when getting unavailable address.") + } + + // Test available address by creating a random string 32 characters long. + // If anyone *has* an address 32 characters long, there is a 1 in 6.33 x 10^49 chance that it will match and this test will fail + availableDomain := randStringBytes(32) + a, err := c.GetAddressAvailability(availableDomain) + if err != nil { + t.Errorf(err.Error()) + } + + if a != nil { + t.Logf("%+v\n", *a) + + if a.Address != availableDomain { + t.Errorf("Returned address: %s did not match expected address %s.", a.Address, availableDomain) + } + if strings.ToLower(a.Availability) != "available" { + t.Errorf("Returned availability: %s did not match expected 'available'.", u.Availability) + } + if a.Available != true { + t.Errorf("Return available field was %t, expected true.", u.Available) + } + } else { + t.Error("Address Availability returned 'nil' when getting available address.") + } + } func TestGetAddressExpiration(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { t.Errorf(err.Error()) } - a, err := c.GetAddressExpiration("test") + e, err := c.GetAddressExpiration(testOwnedDomain) if err != nil { t.Errorf(err.Error()) } - t.Logf("%+v\n", *a) + if e != nil { + t.Logf("Expiration for domain: '%s' returned: '%t'.", testOwnedDomain, *e) + if *e != false { + t.Errorf("Expected expiration to be false, got %t.", *e) + } + } else { + t.Errorf("Expiration returned 'nil'.") + } } func TestGetAddressInfo(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { t.Errorf(err.Error()) } - i, err := c.GetAddressInfo("terraform") + i, err := c.GetAddressInfo(testOwnedDomain) if err != nil { t.Errorf(err.Error()) } - t.Logf("%+v\n", *i) + if i != nil { + t.Logf("Address Info: %+v\n", *i) + + if i.Address != testOwnedDomain { + t.Errorf("Expected Address: %s, got %s", testOwnedDomain, i.Address) + } + if len(i.Message) <= 0 { + t.Error("Expected Message to not be empty.") + } + testTimestamps(t, i.Registration.UnixEpochTime, i.Registration.Iso8601Time, i.Registration.Rfc2822Time, i.Registration.RelativeTime) + testTimestamps(t, i.Expiration.UnixEpochTime, i.Expiration.Iso8601Time, i.Expiration.Rfc2822Time, i.Expiration.RelativeTime) + if i.Verification.Verified { + if i.Verification.Message != "This address has been verified." { + t.Errorf("Unexpected message when Verified == `true`: %s", i.Verification.Message) + } + } else { + if i.Verification.Message != "This address has not been verified." { + t.Errorf("Unexpected message when Verified == `false`: %s", i.Verification.Message) + } + } + if len(i.Owner) <= 0 { + t.Error("Unexpected empty Owner.") + } + } else { + t.Error("Address info returned 'nil'.") + } } func TestGetAddressDirectory(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -61,5 +138,12 @@ func TestGetAddressDirectory(t *testing.T) { t.Errorf(err.Error()) } - t.Logf("%+v\n", *d) + if d != nil { + t.Logf("Address Directory: %+v\n", *d) + if len(d.Directory) <= 0 { + t.Error("Directory returned empty.") + } + } else { + t.Error("Address directory returned 'nil'.") + } } diff --git a/omglol/common_test.go b/omglol/common_test.go index 72dfd83..f45044c 100644 --- a/omglol/common_test.go +++ b/omglol/common_test.go @@ -2,10 +2,15 @@ package omglol import ( "fmt" + "math/rand" "os" + "testing" "time" ) +// time in seconds to sleep before running each test. Avoids hitting rate limits +const initPause int64 = 1 + var testEmail = os.Getenv("OMGLOL_USER_EMAIL") var testKey = os.Getenv("OMGLOL_API_KEY") var testName = os.Getenv("OMGLOL_USERNAME") @@ -21,6 +26,11 @@ func setHostURL() string { var testHostURL = setHostURL() +// Add sleep to tests to avoid hitting rate limits when accessing the API +func sleep() { + time.Sleep(time.Duration(initPause) * time.Second) +} + // Generates a UID from the Github Workflow if present, otherwise generates a random string. This UID can then be used to prevent collision between test runs. func generateRunUID() string { RunUID := os.Getenv("GITHUB_RUN_ID") + os.Getenv("GITHUB_RUN_ATTEMPT") @@ -31,3 +41,48 @@ func generateRunUID() string { } var RunUID = generateRunUID() + +func isOneOf(target string, list []string) bool { + for _, s := range list { + if s == target { + return true + } + } + return false +} + +func testTimestamps(t *testing.T, unix int64, iso8601, rfc2822, relative string) { + const RFC2822 = "Mon, 02 Jan 2006 15:04:05 -0700" + + u := time.Unix(unix, 0) + if u.IsZero() { + t.Errorf("Invalid UnixEpochTime: %d", unix) + } + i8601, err := time.Parse(time.RFC3339, iso8601) + if err != nil { + t.Errorf("Invalid Iso8601Time: %s", iso8601) + } + if u.Unix() != i8601.Unix() { + t.Errorf("UnixEpochTime: %d, does not match Iso8601Time: %d", u.Unix(), i8601.Unix()) + } + r2822, err := time.Parse(RFC2822, rfc2822) + if err != nil { + t.Errorf("Invalid Rfc2822Time: %s, %e", rfc2822, err) + } + if u.Unix() != r2822.Unix() { + t.Errorf("UnixEpochTime: %d, does not match Rfc2822Time: %d", u.Unix(), i8601.Unix()) + } + if len(relative) <= 0 { + t.Errorf("Invalid RelativeTime: %s", relative) + } +} + +func randStringBytes(n int) string { + const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" + rand.Seed(time.Now().UnixNano()) + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return string(b) +} \ No newline at end of file diff --git a/omglol/dns.go b/omglol/dns.go index 9697819..07cef96 100644 --- a/omglol/dns.go +++ b/omglol/dns.go @@ -16,8 +16,13 @@ func (c *Client) ListDNSRecords(address string) (*[]DNSRecord, error) { return nil, err } + var d []DNSRecord + body, err := c.doRequest(req) if err != nil { + if strings.Contains(err.Error(), "status: 404") { + return &d, nil + } return nil, err } @@ -35,7 +40,9 @@ func (c *Client) ListDNSRecords(address string) (*[]DNSRecord, error) { return nil, err } - return &r.Response.DNS, nil + d = r.Response.DNS + + return &d, nil } // Find a single DNS record from its attributes. @@ -180,7 +187,7 @@ func convertRecordResponse(r dnsRecordContent) *DNSRecord { } // Returns a string representaion of a DNS record -func (d *DNSRecord) ToString() string { +func (d *DNSRecord) String() string { priority := "" if d.Priority != nil { diff --git a/omglol/dns_test.go b/omglol/dns_test.go index 10c9d80..2755b8c 100644 --- a/omglol/dns_test.go +++ b/omglol/dns_test.go @@ -2,9 +2,52 @@ package omglol import ( "testing" + "time" ) +func validateRecord(t *testing.T, r DNSRecord) { + if r.ID <= 0 { + t.Errorf("Record ID is invalid.") + } + if !isOneOf(r.Type, []string{"A", "AAAA", "CAA", "CNAME", "MX", "NS", "SRV", "TXT"}) { + t.Errorf("Unexpected record type: %s.", r.Type) + } + if len(r.Name) <= 0 { + t.Errorf("Name is empty.") + } + if len(r.Data) <= 0 { + t.Errorf("Data is empty.") + } + if r.Type == "MX" && r.Priority == nil { + t.Errorf("Priority cannot be nil for record type 'MX'.") + } + if r.Type != "MX" && r.Priority != nil { + t.Errorf("Record of type '%s' should not have a priority not equal to 'nil'.", r.Type) + } + if r.TTL <= 0 { + t.Errorf("TTL must be greater than 0, got %d", r.TTL) + } + created, err := time.Parse(time.RFC3339, r.CreatedAt) + if err != nil { + t.Error(err.Error()) + } + if created.Unix() <= 0 { + t.Error("Invalid CreatedAt timestamp.") + } + updated, err := time.Parse(time.RFC3339, r.UpdatedAt) + if err != nil { + t.Error(err.Error()) + } + if updated.Unix() <= 0 { + t.Error("Invalid UpdatedAt timestamp.") + } + if updated.Unix() < created.Unix() { + t.Errorf("Updated: %s, cannot be before Created: %s.", r.UpdatedAt, r.CreatedAt) + } +} + func TestListDNSRecords(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -16,46 +59,63 @@ func TestListDNSRecords(t *testing.T) { t.Errorf(err.Error()) } - for _, d := range *l { - t.Logf(d.ToString() + "\n") + if l != nil { + for _, d := range *l { + t.Logf(d.String() + "\n") + validateRecord(t, d) + } + } else { + t.Error("ListDNSRecords returned 'nil'.") } + } func TestFilterDNSRecords(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) - if err != nil { t.Errorf(err.Error()) } // TXT Record criteria1 := map[string]any{ - "ID": 41923511, + "Name": "testlistdns." + testOwnedDomain, "Type": "TXT", "TTL": 300, "Priority": nil, } d1, err := c.FilterDNSRecord(testOwnedDomain, criteria1) - if err != nil { t.Errorf(err.Error()) } - t.Logf(d1.ToString()) + if d1 != nil { + t.Logf(d1.String()) + validateRecord(t, *d1) + } else { + t.Errorf("FilterDNSRecord returned nil.") + } // MX Record criteria2 := map[string]any{ - "ID": int64(42197707), + "Type": "MX", + "Name": "mail." + testOwnedDomain, "Priority": int64(20), } d2, err := c.FilterDNSRecord(testOwnedDomain, criteria2) - t.Logf(d2.ToString()) + if d2 != nil { + t.Logf(d2.String()) + validateRecord(t, *d2) + } else { + t.Errorf("FilterDNSRecord returned nil.") + } } func TestCreateAndDeleteDNSRecord(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -69,7 +129,12 @@ func TestCreateAndDeleteDNSRecord(t *testing.T) { t.Errorf(err.Error()) } - t.Logf(r.ToString()) + if r != nil { + t.Logf(r.String()) + validateRecord(t, *r) + } else { + t.Error("CreateDNSRecord returned 'nil'.") + } err = c.DeleteDNSRecord(testOwnedDomain, r.ID) if err != nil { @@ -78,6 +143,7 @@ func TestCreateAndDeleteDNSRecord(t *testing.T) { } func TestCreateUpdateDeleteDNSRecord(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -93,16 +159,27 @@ func TestCreateUpdateDeleteDNSRecord(t *testing.T) { t.Errorf(err.Error()) } - t.Logf(create.ToString()) - - replace, err := c.UpdateDNSRecord(testOwnedDomain, *record2, create.ID) + if create != nil { + t.Logf(create.String()) + validateRecord(t, *create) + } else { + t.Error("CreateDNSRecord returned 'nil'.") + } + sleep() + update, err := c.UpdateDNSRecord(testOwnedDomain, *record2, create.ID) if err != nil { t.Errorf(err.Error()) } - t.Logf(replace.ToString()) + if update != nil { + t.Logf(update.String()) + validateRecord(t, *update) + } else { + t.Error("UpdateDNSRecord returned 'nil'.") + } - err = c.DeleteDNSRecord(testOwnedDomain, replace.ID) + sleep() + err = c.DeleteDNSRecord(testOwnedDomain, update.ID) if err != nil { t.Errorf(err.Error()) } diff --git a/omglol/models.go b/omglol/models.go index 26d2afb..49f44fb 100644 --- a/omglol/models.go +++ b/omglol/models.go @@ -14,19 +14,29 @@ type apiResponse struct { Response json.RawMessage `json:"response"` } -type Registration struct { - Message string `json:"message,omitempty"` - UnixEpochTime int64 `json:"unix_epoch_time,omitempty"` - Iso8601Time string `json:"iso_8601_time,omitempty"` - Rfc2822Time string `json:"rfc_2822_time,omitempty"` - RelativeTime string `json:"relative_time,omitempty"` +type AddressRegistration struct { + Message string `json:"message"` + UnixEpochTime int64 `json:"unix_epoch_time"` + Iso8601Time string `json:"iso_8601_time"` + Rfc2822Time string `json:"rfc_2822_time"` + RelativeTime string `json:"relative_time"` +} + +type AddressExpiration struct { + Message string `json:"message"` + Expired bool `json:"expired"` + WillExpire bool `json:"will_expire"` + UnixEpochTime int64 `json:"unix_epoch_time"` + Iso8601Time string `json:"iso_8601_time"` + Rfc2822Time string `json:"rfc_2822_time"` + RelativeTime string `json:"relative_time"` } type AccountSettings struct { - Owner string `json:"owner,omitempty"` - Communication string `json:"communication,omitempty"` - DateFormat string `json:"date_format,omitempty"` - WebEditor string `json:"web_editor,omitempty"` + Owner string `json:"owner,omitempty"` + Communication *string `json:"communication,omitempty"` + DateFormat *string `json:"date_format,omitempty"` + WebEditor *string `json:"web_editor,omitempty"` } type Account struct { @@ -52,13 +62,13 @@ type ActiveSession struct { } type Address struct { - Address string `json:"address"` - Message string `json:"message,omitempty"` - Punycode string `json:"punycode,omitempty"` - SeeAlso string `json:"see-also,omitempty"` - Registration Registration `json:"registration"` - Expiration AddressExpiration `json:"expiration"` - Owner string `json:"owner,omitempty"` + Address string `json:"address"` + Message string `json:"message,omitempty"` + Punycode string `json:"punycode,omitempty"` + SeeAlso string `json:"see-also,omitempty"` + Registration AddressRegistration `json:"registration"` + Expiration AddressExpiration `json:"expiration"` + Owner string `json:"owner,omitempty"` } type AddressAvailability struct { @@ -68,21 +78,11 @@ type AddressAvailability struct { Availability string `json:"availability"` } -type AddressExpiration struct { - Message string `json:"message,omitempty"` - Expired bool `json:"expired,omitempty"` - WillExpire bool `json:"will_expire,omitempty"` - UnixEpochTime int64 `json:"unix_epoch_time,omitempty"` - Iso8601Time string `json:"iso_8601_time,omitempty"` - Rfc2822Time string `json:"rfc_2822_time,omitempty"` - RelativeTime string `json:"relative_time,omitempty"` -} - type AddressInfo struct { - Address string `json:"address"` - Message string `json:"message"` - Registration Registration `json:"registration"` - Expiration AddressExpiration `json:"expiration"` + Address string `json:"address"` + Message string `json:"message"` + Registration AddressRegistration `json:"registration"` + Expiration AddressExpiration `json:"expiration"` Verification struct { Message string `json:"message"` Verified bool `json:"verified"` @@ -122,7 +122,7 @@ type dnsRecordContent struct { Type string `json:"type"` Name string `json:"name"` Content string `json:"content"` - Priority *int64 `json:"priority"` + Priority *int64 `json:"priority"` TTL int64 `json:"ttl"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` diff --git a/omglol/purl.go b/omglol/purl.go index 3615434..e0b66eb 100644 --- a/omglol/purl.go +++ b/omglol/purl.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "strconv" + "strings" ) // Create a PersistentURL object @@ -25,7 +26,7 @@ func NewPersistentURL(Name, URL string, listed bool, Counter ...*int64) *Persist } // Returns a string representaion of a PersistentURL -func (p *PersistentURL) ToString() string { +func (p *PersistentURL) String() string { counter := "" if p.Counter != nil { counter = strconv.Itoa(int(*p.Counter)) @@ -35,21 +36,21 @@ func (p *PersistentURL) ToString() string { // Create a new PersistentURL. See https://api.omg.lol/#token-post-purls-create-a-new-purl func (c *Client) CreatePersistentURL(domain string, purl PersistentURL) error { - + type purlRequest struct { - Name string `json:"name"` - URL string `json:"url"` - Listed *bool `json:"listed"` + Name string `json:"name"` + URL string `json:"url"` + Listed *bool `json:"listed"` } p := purlRequest{ Name: purl.Name, - URL: purl.URL, + URL: purl.URL, } if !purl.Listed { p.Listed = nil - } else { + } else { t := true p.Listed = &t } @@ -139,8 +140,13 @@ func (c *Client) ListPersistentURLs(address string) (*[]PersistentURL, error) { return nil, err } + var p []PersistentURL + body, err := c.doRequest(req) if err != nil { + if strings.Contains(err.Error(), "status: 404") { + return &p, nil + } return nil, err } @@ -166,8 +172,6 @@ func (c *Client) ListPersistentURLs(address string) (*[]PersistentURL, error) { return nil, err } - var p []PersistentURL - for _, purl := range r.Response.PURLs { var x PersistentURL diff --git a/omglol/purl_test.go b/omglol/purl_test.go index d4916f2..55fd62f 100644 --- a/omglol/purl_test.go +++ b/omglol/purl_test.go @@ -4,7 +4,20 @@ import ( "testing" ) +func validatePersistentURL(t *testing.T, p PersistentURL) { + if len(p.Name) <= 0 { + t.Error("PURL Name is empty.") + } + if len(p.URL) <= 0 { + t.Error("PURL URL is empty.") + } + if p.Counter == nil { + t.Error("PURL Counter should not be 'nil'.") + } +} + func TestGetPersistentURL(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -16,10 +29,16 @@ func TestGetPersistentURL(t *testing.T) { t.Errorf(err.Error()) } - t.Logf(p.ToString()) + if p != nil { + t.Logf(p.String()) + validatePersistentURL(t, *p) + } else { + t.Error("GetPersistentURL returned 'nil'.") + } } func TestListPersistentURLs(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -31,12 +50,19 @@ func TestListPersistentURLs(t *testing.T) { t.Errorf(err.Error()) } - for _, p := range *l { - t.Logf(p.ToString() + "\n") + if l != nil { + for _, p := range *l { + t.Logf(p.String() + "\n") + validatePersistentURL(t, p) + } + } else { + t.Error("ListPersistentURLs returned 'nil'.") } + } func TestCreateAndDeletePersistentURL(t *testing.T) { + sleep() c, err := NewClient(testEmail, testKey, testHostURL) if err != nil { @@ -52,11 +78,18 @@ func TestCreateAndDeletePersistentURL(t *testing.T) { t.Errorf(err.Error()) } - _, err = c.GetPersistentURL(testOwnedDomain, name1) + u, err := c.GetPersistentURL(testOwnedDomain, name1) if err != nil { t.Errorf(err.Error()) } + if u != nil { + t.Log(u.String()) + validatePersistentURL(t, *u) + } else { + t.Error("GetPersistentURL returned 'nil' when retrieving unlisted PURL.") + } + err = c.DeletePersistentURL(testOwnedDomain, name1) if err != nil { t.Errorf(err.Error()) @@ -71,11 +104,18 @@ func TestCreateAndDeletePersistentURL(t *testing.T) { t.Errorf(err.Error()) } - _, err = c.GetPersistentURL(testOwnedDomain, name2) + l, err := c.GetPersistentURL(testOwnedDomain, name2) if err != nil { t.Errorf(err.Error()) } + if l != nil { + t.Log(l.String()) + validatePersistentURL(t, *l) + } else { + t.Error("GetPersistentURL returned 'nil' when retrieving listed PURL.") + } + err = c.DeletePersistentURL(testOwnedDomain, name2) if err != nil { t.Errorf(err.Error())