diff --git a/README.md b/README.md index 10fb412..272ae0f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A simple library to generate URLs for various Taskcluster resources across our v This serves as both a simple shim for projects that use JavaScript but also is the reference implementation for how we define these paths. -URLs are defined in the 'Taskcluster URL Format' document. +URLs are defined in the '[Taskcluster URL Format](https://github.com/taskcluster/taskcluster-lib-urls/tree/master/docs/urls-spec.md)' document. Changelog --------- @@ -40,6 +40,7 @@ root URL: * `apiManifest(rootUrl)` -> `String` * `testRootUrl()` -> `String` * `withRootUrl(rootUrl)` -> `Class` instance for above methods +* `normalizeRootUrl(rootUrl)` -> `String` (the "normalized" form of the given rootUrl) When the `rootUrl` is `https://taskcluster.net`, the generated URLs will be to the Heroku cluster. Otherwise they will follow the [spec defined in this project](https://github.com/taskcluster/taskcluster-lib-urls/tree/master/docs/urls-spec.md). @@ -113,6 +114,7 @@ func ExchangesReferenceSchema(rootURL string, version string) string func MetadataMetaschema(rootURL string) string func UI(rootURL string, path string) string func APIManifest(rootURL string) string +func NormalizedRootURL(rootURL string) string ``` Install with: @@ -146,6 +148,7 @@ taskcluster_urls.exchange_reference(root_url, 'auth', 'v1') taskcluster_urls.ui(root_url, 'foo/bar') taskcluster_urls.apiManifest(root_url) taskcluster_urls.docs(root_url, 'foo/bar') +taskcluster_urls.normalized_root_url(root_url) And for testing, ```python diff --git a/docs/urls-spec.md b/docs/urls-spec.md index 149bf16..6dbb387 100644 --- a/docs/urls-spec.md +++ b/docs/urls-spec.md @@ -13,6 +13,10 @@ Example: https://taskcluster.example.com ``` +Normally, a rootUrl has no trailing `/` characters. +We suggest that libraries and tools be lenient and accept rootUrls containing a trailing `/`, but produce rootUrls without a trailing `/`. +The `normalizeRootUrl` function supports this practice. + # URLs Taskcluster uses URLs with the following pattern: diff --git a/src/index.js b/src/index.js index 1c6a3a1..deec34f 100644 --- a/src/index.js +++ b/src/index.js @@ -290,4 +290,11 @@ module.exports = { testRootUrl() { return 'https://tc-tests.example.com'; }, + + /** + * Return the normal form of this rootUrl + */ + normalizeRootUrl(rootUrl) { + return cleanRoot(rootUrl); + }, }; diff --git a/taskcluster_urls/__init__.py b/taskcluster_urls/__init__.py index 687882d..cbd04b0 100644 --- a/taskcluster_urls/__init__.py +++ b/taskcluster_urls/__init__.py @@ -88,3 +88,7 @@ def test_root_url(): """Returns a standardized "testing" rootUrl that does not resolve but is easily recognizable in test failures.""" return 'https://tc-tests.example.com' + +def normalize_root_url(root_url): + """Return the normal form of the given rootUrl""" + return root_url.rstrip('/') diff --git a/tcurls.go b/tcurls.go index 53453f9..850181e 100644 --- a/tcurls.go +++ b/tcurls.go @@ -106,3 +106,8 @@ func APIManifest(rootURL string) string { return fmt.Sprintf("%s/references/manifest.json", r) } } + +// NormalizeRootURL returns the normal form of the given rootURL. +func NormalizeRootURL(rootURL string) string { + return strings.TrimRight(rootURL, "/") +} diff --git a/tcurls_test.go b/tcurls_test.go index 18c9c52..ef95337 100644 --- a/tcurls_test.go +++ b/tcurls_test.go @@ -19,6 +19,20 @@ type TestsSpecification struct { Tests []TestCase `yaml:"tests"` } +func getTests() (*TestsSpecification, error) { + data, err := ioutil.ReadFile("tests.yml") + if err != nil { + return nil, err + } + var spec TestsSpecification + err = yaml.Unmarshal([]byte(data), &spec) + if err != nil { + return nil, err + } + + return &spec, nil +} + func testFunc(t *testing.T, function string, expectedURL string, root string, args ...string) { var actualURL string switch function { @@ -56,16 +70,10 @@ func testFunc(t *testing.T, function string, expectedURL string, root string, ar } func TestURLs(t *testing.T) { - data, err := ioutil.ReadFile("tests.yml") + spec, err := getTests() if err != nil { t.Error(err) } - var spec TestsSpecification - err = yaml.Unmarshal([]byte(data), &spec) - if err != nil { - t.Error(err) - } - for _, test := range spec.Tests { for _, argSet := range test.ArgSets { for cluster, rootURLs := range spec.RootURLs { @@ -77,6 +85,22 @@ func TestURLs(t *testing.T) { } } +func TestNormalize(t *testing.T) { + spec, err := getTests() + if err != nil { + t.Error(err) + } + + expected := spec.RootURLs["new"][0] + for _, rootURL := range spec.RootURLs["new"] { + actual := NormalizeRootURL(rootURL) + if expected != actual { + t.Errorf("%v NormalizeRootURL(%v) = `%v` but should be `%v`", redCross(), rootURL, actual, expected) + } + t.Logf("%v NormalizeRootURL(%v) = `%v`", greenTick(), rootURL, actual) + } +} + // quotedList returns a backtick-quoted list of the arguments passed in func quotedList(url string, args []string) string { all := append([]string{url}, args...) diff --git a/test/basic_test.js b/test/basic_test.js index e65f284..c2d6aa8 100644 --- a/test/basic_test.js +++ b/test/basic_test.js @@ -5,14 +5,13 @@ const yaml = require('js-yaml'); const libUrls = require('../'); const SPEC_FILE = path.join(__dirname, '../tests.yml'); +const TESTS = yaml.safeLoad(fs.readFileSync(SPEC_FILE, {encoding: 'utf8'})); suite('basic test', function() { - - var doc = yaml.safeLoad(fs.readFileSync(SPEC_FILE, {encoding: 'utf8'})); - for (let t of doc['tests']) { + for (let t of TESTS['tests']) { for (let argSet of t['argSets']) { - for (let cluster of Object.keys(doc['rootURLs'])) { - for (let rootURL of doc['rootURLs'][cluster]) { + for (let cluster of Object.keys(TESTS['rootURLs'])) { + for (let rootURL of TESTS['rootURLs'][cluster]) { test(`${t['function']} - ${argSet}`, function() { assert.equal(t['expected'][cluster], libUrls.withRootUrl(rootURL)[t['function']](...argSet)); assert.equal(t['expected'][cluster], libUrls[t['function']](rootURL, ...argSet)); @@ -22,3 +21,12 @@ suite('basic test', function() { } } }); + +suite('normalization', function() { + const correct = TESTS.rootURLs['new'][0]; + for (let rootUrl of TESTS.rootURLs['new']) { + test(`normalize ${rootUrl}`, function() { + assert.equal(libUrls.normalizeRootUrl(rootUrl), correct); + }); + } +}); diff --git a/test/normalize_test.py b/test/normalize_test.py new file mode 100644 index 0000000..35ba863 --- /dev/null +++ b/test/normalize_test.py @@ -0,0 +1,24 @@ +import os +import yaml +import taskcluster_urls as tcurls + + +SPEC_FILE = os.path.join(os.path.dirname(__file__), '..', 'tests.yml') + + +def pytest_generate_tests(metafunc): + with open(SPEC_FILE) as testsFile: + spec = yaml.load(testsFile) + root_urls = spec['rootURLs']['new'] + expected_url = root_urls[0] + metafunc.parametrize( + ['expected_url', 'root_url'], + [ + (expected_url, root_url) + for root_url in root_urls + ] + ) + + +def test_normalize(expected_url, root_url): + assert tcurls.normalize_root_url(root_url) == expected_url