Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is-13 add basic API tests #842

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open

Conversation

pkeroulas
Copy link

Successfully tested against:
garethsb/nmos-cpp@04ac4fd
AMWA-TV/is-13@8655e0c

When possible, TestResult.detail should be displayed in stderr since the
raised exception prevents from the report creation.
Copy link
Contributor

@peterbrightwell peterbrightwell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pat demonstrated this on a resource labelling call and it looks good

For each resource&objects:

- Read initial value and store
- Reset default value, check timestamp and store
- Write max-length and check value+timestamp
- Write >max-length and check value+timestamp
- Reset default value and compare
- Restore initial value

Apply to objects: label, description, tags

TODO: check IS04
@pkeroulas
Copy link
Author

@garethsb @jonathan-r-thorpe please, can you have a 1st look at this?

RESOURCES = ["self", "devices", "senders", "receivers"]
OBJECTS = ["label", "description", "tags"]

# const for label&description-related tests
Copy link
Contributor

@jonathan-r-thorpe jonathan-r-thorpe Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# const for label&description-related tests
# Constants for label- and description-related tests

STRING_OVER_MAX_VALUE = ''.join(['X' for i in range(100)])
STRING_MAX_VALUE = STRING_OVER_MAX_VALUE[:64] # this is the max length tolerated

# const for tags-related tests
Copy link
Contributor

@jonathan-r-thorpe jonathan-r-thorpe Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# const for tags-related tests
# Constants for tags-related tests

Comment on lines +54 to +55
STRING_OVER_MAX_VALUE = ''.join(['X' for i in range(100)])
STRING_MAX_VALUE = STRING_OVER_MAX_VALUE[:64] # this is the max length tolerated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter naming here gives the first impression these are number values (cf. INT_MAX).

Perhaps add LENGTH in as well STRING_MAX_LENGTH_VALUE? A little verbose but less confusing.

Similarly STRING_OVER_MAX_VALUE could be STRING_LENGTH_OVER_MAX_VALUE or STRING_OVER_MAX_LENGTH_VALUE

Comment on lines +58 to +59
TAGS_OVER_MAX_VALUE = {'location': ['underground'], 'studio': ['42'], 'tech': ['John', 'Mike']}
TAGS_MAX_VALUE = TAGS_OVER_MAX_VALUE.copy()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment for STRING_OVER_MAX_VALUE applies here also.

Comment on lines +86 to +97
"""
FAKE_ORIG = {
'description': 'fake_orig_desc',
'label': 'fake_orig_label',
'tags': {
'location': ['fake_location']
}
}
for resource in RESOURCES:
url = "{}{}{}".format(self.annotation_url, 'node/', resource)
TestHelper.do_request("PATCH", url, json=FAKE_ORIG)
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant code needs removing.

Comment on lines +165 to +166
def log(self, msg):
print(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally the test suites don't print to the console as the console reporting is handled by the testing framework, so the "logging" is best removed.

Comment on lines +69 to +70
def strip_tags(tags):
if TAGS_TO_BE_SKIPPED in list(tags.keys()):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped or stripped? TAGS_TO_BE_STRIPPED perhaps?

else:
return False, "GET Resquest FAIL"

def compare_resource(self, object, new, resp):
Copy link
Contributor

@jonathan-r-thorpe jonathan-r-thorpe Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess resp here is response, which doesn't make much sense in the context of a comparison function.

Would prefer this to be consistent with other compare functions in the code base (e.g. NMOSUtils.compare_urls, NMOSUtils.compare_api_version etc.) that, (rightly or wrongly) use (url1, url2) and (ver1, ver2), so (res1, res2) might be preferable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also object both an overly broad and overloaded term; is there something more descriptive that could be used here? tag_type?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree "object" (and OBJECTS for the list of them) etc. seems confusing terminology. I think we're talking about what IS-13 calls the "annotation properties"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also agree new and resp are confusing, but this function doesn't seem symmetric, which means it's not a usual comparison function... 😕 @jonathan-r-thorpe, let's discuss while we're face-to-face this week!

""" Patch a resource with one ore several object values """

object = list(new.keys())[0]
valid, resp = TestHelper.do_request("PATCH", url, json=new)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
valid, resp = TestHelper.do_request("PATCH", url, json=new)
valid, resp = self.do_request("PATCH", url, json=new)

Comment on lines +133 to +134
if not valid:
return False, "PATCH Resquest FAIL"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than pass back a flag and message, you could raise NMOSTestExceptions in this function. (See do_receiver_put in IS0401Test.py for some examples of wrapping the test failure in this exception). You could then remove the flag handling code in do_test making it more compact and readable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I'd like to see this done consistently with the rest of the test suites, and functions named in a way which indicates whether they return TestResults, raise NMOSTestExceptions, or a boolean and (optional) strings.

Comment on lines +168 to +170
def get_url(self, base_url, resource):
"""
Build the url for both annotation and node APIs which behaves differently.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting or building? looks more a build to me; idiomatically "create" is used a lot in the other test suites for this sort of thing create_url perhaps?

Comment on lines +17 to +19
The script implements the IS-13 test suite as specified by the nmos-resource-labelling workgroup.
At the end of the test, the initial state of the tested unit is supposed to be restored but this
cannot be garanteed.
Copy link
Contributor

@jonathan-r-thorpe jonathan-r-thorpe Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing the spec is the true "source of truth" here. Where possible it would be great to tie test cases back to normative language in the specifications (you can supply a link= to the test failure case that will embed a url link in the test results seen by the user).

Suggested change
The script implements the IS-13 test suite as specified by the nmos-resource-labelling workgroup.
At the end of the test, the initial state of the tested unit is supposed to be restored but this
cannot be garanteed.
The script implements the IS-13 test suite according to the AMWA IS-13 NMOS Annotation Specification (https://specs.amwa.tv/is-13/).
At the end of the test, the initial state of the tested unit is supposed to be restored but this
cannot be guaranteed.

@jonathan-r-thorpe
Copy link
Contributor

A general question - looking at the specification there are a few normative references not covered by this test suite. Will these be covered before the activity closes?
e.g. "The Annotation API SHOULD be advertised as a ‘service’ endpoint under an IS-04 NMOS Node in the services array" https://specs.amwa.tv/is-13/branches/v1.0-dev/docs/Interoperability_-_IS-04.html#discovery,
"the UUIDs of Node Resources in the Annotation API MUST match those in the corresponding IS-04 Node API"
https://specs.amwa.tv/is-13/branches/v1.0-dev/docs/Interoperability_-_IS-04.html#consistent-resources

@pkeroulas
Copy link
Author

The first spec is not covered indeed, but the second is. I still need to understand the translation of normative SHOULD to code. Thanks @jonathan-r-thorpe

Copy link
Contributor

@garethsb garethsb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few comments, which I'll review with @jonathan-r-thorpe since we're face-to-face today!

else:
return False, "GET Resquest FAIL"

def compare_resource(self, object, new, resp):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree "object" (and OBJECTS for the list of them) etc. seems confusing terminology. I think we're talking about what IS-13 calls the "annotation properties"?

except Exception as e:
return False, e.msg
else:
return False, "GET Resquest FAIL"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least a typo, but also want to check why this suite needs a function like this if other suites don't, or whether there is already a common approach to this...

Suggested change
return False, "GET Resquest FAIL"
return False, "GET Request FAIL"

else:
return False, "GET Resquest FAIL"

def compare_resource(self, object, new, resp):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also agree new and resp are confusing, but this function doesn't seem symmetric, which means it's not a usual comparison function... 😕 @jonathan-r-thorpe, let's discuss while we're face-to-face this week!

Comment on lines +133 to +134
if not valid:
return False, "PATCH Resquest FAIL"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I'd like to see this done consistently with the rest of the test suites, and functions named in a way which indicates whether they return TestResults, raise NMOSTestExceptions, or a boolean and (optional) strings.

object = list(new.keys())[0]
valid, resp = TestHelper.do_request("PATCH", url, json=new)
if not valid:
return False, "PATCH Resquest FAIL"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return False, "PATCH Resquest FAIL"
return False, "PATCH Resuest FAIL"


def do_test(self, test, resource, object):
"""
Perform the test sequence as documented in the file header
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems more natural to see the explanation of this function here to me?

# re-GET
valid, resp = self.get_resource(url)
if not valid:
return False, "Get Resquest FAIL"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return False, "Get Resquest FAIL"
return False, "Get Request FAIL"

return self.do_test(test, "receivers", "description")

def test_04_03(self, test):
"""Annotation test: receivers/sevices/../tags (reset to default, set 5 tags, set >5 tags, check IS04+version)"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

return self.do_test(test, "senders", "description")

def test_03_03(self, test):
"""Annotation test: sender/sevices/../tags (reset to default, set 5 tags, set >5 tags, check IS04+version)"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

return test.PASS()

def test_01_01(self, test):
""" Annotation test: self/label (reset to default, set 64-byte value, set >64-byte, check IS04+version)"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These strings appear in the UI to describe the test, so let's wordsmith somewhat, @jonathan-r-thorpe?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants