Skip to content

Commit

Permalink
Add support for TestRail timespan type
Browse files Browse the repository at this point in the history
Support TestRail's string-based timestamp types:
- Allow converting values such as "1w 2d" to and from native time.Duration values
- Add a unit test for marshaling and unmarshaling
  • Loading branch information
gavrie committed Oct 13, 2016
1 parent 48a22e6 commit 4cb42ad
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 0 deletions.
88 changes: 88 additions & 0 deletions timespan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package testrail

import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
)

// Timespan field type.
// e.g. "1m" or "2m 30s" -- similar to time.Duration
//
// For a description, see:
// http://docs.gurock.com/testrail-api2/reference-results

type timespan struct {
time.Duration
}

// Unmarshal TestRail timespan into a time.Duration.
// Transform TestRail-specific formats into something time.ParseDuration understands:
// "4h 5m 6s" => "4h5m6s"
// "1d" => "8h"
// "1w" => "40h"
// "1d 2h" => "8h2h"
// "1w 2d 3h" => "40h16h3h"
func (tsp *timespan) UnmarshalJSON(data []byte) error {
const (
// These are hardcoded in TestRail.
// https://discuss.gurock.com/t/estimate-fields/900
daysPerWeek = 5
hoursPerDay = 8
)
var (
err error
span string
)

if err = json.Unmarshal(data, &span); err != nil {
return err
}

if span == "" {
return nil
}

var parts []string
for _, p := range strings.Fields(span) {
if len(p) < 2 {
return fmt.Errorf("%q: sequence is too short", p)
}
amount, err := strconv.Atoi(p[:len(p)-1])
if err != nil {
return fmt.Errorf("%q: cannot convert to int: %v", amount, err)
}
unit := p[len(p)-1]
switch unit {
case 'd':
unit = 'h'
amount *= hoursPerDay
case 'w':
unit = 'h'
amount *= daysPerWeek * hoursPerDay
}
parts = append(parts, fmt.Sprintf("%v%c", amount, unit))
}

tsp.Duration, err = time.ParseDuration(strings.Join(parts, ""))
if err != nil {
return fmt.Errorf("%q: %v", span, err)
}
return nil
}

func (tsp timespan) MarshalJSON() ([]byte, error) {
d := tsp.Duration

if d == 0 {
return []byte(`null`), nil
}

h, d := d/time.Hour, d%time.Hour
m, d := d/time.Minute, d%time.Minute
s := d / time.Second

return []byte(fmt.Sprintf(`"%dh %dm %ds"`, h, m, s)), nil
}
75 changes: 75 additions & 0 deletions timespan_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package testrail

import (
"encoding/json"
"fmt"
"testing"
"time"
)

func TestTimespanUnmarshal(t *testing.T) {
var testData = []struct{ json, stringDuration string }{
{`null`, "0s"},
{`"15s"`, "15s"},
{`"12m"`, "12m"},
{`"11h"`, "11h"},
{`"4h 5m 6s"`, "4h5m6s"},
{`"1d"`, "8h"},
{`"1w"`, "40h"},
{`"1d 2h"`, "8h2h"},
{`"1w 2d 3h"`, "40h16h3h"},
}

for _, data := range testData {
var results []Result
js := []byte(fmt.Sprintf(`[{"elapsed":%v}]`, data.json))
if err := json.Unmarshal(js, &results); err != nil {
t.Fatal(err)
}

r := results[0]
expected, err := time.ParseDuration(data.stringDuration)
if err != nil {
t.Fatal(err)
}

if r.Elapsed.Duration != expected {
t.Fatalf("Wrong duration: %v", r.Elapsed.Duration)
}
}
}

func TestTimespanMarshal(t *testing.T) {
var testData = []struct{ json, stringDuration string }{
{`null`, "0s"},
{`"0h 0m 15s"`, "15s"},
{`"0h 12m 0s"`, "12m"},
{`"11h 0m 0s"`, "11h"},
{`"4h 5m 6s"`, "4h5m6s"},
{`"8h 0m 0s"`, "8h"},
{`"40h 0m 0s"`, "40h"},
{`"10h 0m 0s"`, "8h2h"},
{`"59h 0m 0s"`, "40h16h3h"},
}

for _, td := range testData {
duration, err := time.ParseDuration(td.stringDuration)
if err != nil {
t.Fatal(err)
}

result := Result{
Elapsed: timespan{
Duration: duration,
},
}
data, err := json.Marshal(result.Elapsed)
if err != nil {
t.Fatal(err)
}

if string(data) != td.json {
t.Fatalf("Wrong data: %v", string(data))
}
}
}

0 comments on commit 4cb42ad

Please sign in to comment.