Search code examples
pythonrallypyral

Adding attachments to TestCaseResults using pyral 0.9.3


I'm trying to add an attachment to a testcaseresult using pyral like this:

   testCaseResult = rally.create('TestCaseResult', {'TestCase': tc.ref , 'Build': revision,
                          'Verdict': verdict[test.result], 'Date': resultdate, 'Notes': note,
                          'Tester': tester, 'Duration': runtime })


   res = rally.addAttachment(testCaseResult.oid, file);

The TestaseResult is successfully created but res is False.

What am I doing wrong? Should I not be using the oid? I've tried passing testCaseResult, testCaseResult.oid and "TestCaseResult/" + testCaseResult.oid, and none seem to work...

UPDATED:

Based on Mark's answer below (pyral does not directly support adding attachments to testcaseresults), I wrote the following subroutine:

def addTestCaseResultAttachment(testCaseResult, filename, contentType='text/plain'):
    if not os.path.exists(filename):
        raise Exception('Named attachment filename: %s not found' % filename)
    if not os.path.isfile(filename):
        raise Exception('Named attachment filename: %s is not a regular file' % filename)

    attachment_file_name = os.path.basename(filename)
    attachment_file_size = os.path.getsize(filename)

    if attachment_file_size == 0:
        raise Exception('Cannot attach zero length file')

    if attachment_file_size > 5000000:
        raise Exception('Attachment file size too large, unable to attach to Rally Artifact')

    contents = ''
    with open(filename, 'r') as af:
        contents = base64.encodestring(af.read())

    # create an AttachmentContent item
    ac = rally.create('AttachmentContent', {"Content" : contents}, project=None)
    if not ac:
        raise RallyRESTAPIError('Unable to create AttachmentContent for %s' % attachment_file_name)

    attachment_info = { "Name"              :  attachment_file_name,
                        "Content"           :  ac.ref,       # ref to AttachmentContent
                        "ContentType"       :  contentType,
                        "Size"              :  attachment_file_size, # must be size before encoding!!
                        "User"              :  'user/%s' % me.oid,
                        "TestCaseResult"    :  testCaseResult.ref
                      }

    # and finally, create the Attachment
    attachment = rally.create('Attachment', attachment_info, project=None)
    if not attachment:
        raise RallyRESTAPIError('Unable to create Attachment for %s' % attachment_file_name)

Solution

  • The issue is that the addAttachment method for pyral's restapi, sets a ref to the "Artifact" attribute of the Attachment object, as shown following (lines 1241 thru 1251 of restapi):

        attachment_info = { "Name"        :  attachment_file_name,
                            "Content"     :  ac.ref,       # ref to AttachmentContent
                            "ContentType" :  mime_type,    
                            "Size"        :  attachment_file_size, # must be size before encoding!!
                            "User"        :  'user/%s' % self.contextHelper.user_oid,
                           #"Artifact"    :  artifact.ref  # (Artifact is an 'optional' field)
                          }
    
        # While it's actually possible to have an Attachment not linked to an Artifact,
        # in most cases, it'll be far more useful to have the linkage to an Artifact than not.
        if artifact:  
            attachment_info["Artifact"] = artifact.ref
    

    Thus, the addAttachment method will actually only work for objects that inherit from Artifact, i.e. Stories, Defects, Tasks, TestCases, etc. As seen in WSAPI Docs, to associate an Attachment to a TestCaseResult, the needed attribute is actually "TestCaseResult". This syntax was chosen since a TestCaseResult is actually a WorkspaceDomainObject, and not an Artifact.

    Here's an example that creates a new TestCaseResult and adds an Attachment

    #!/usr/bin/env python
    
    #################################################################################################
    #
    # create_tcr_and_attachment.py -- Create a New TestCaseResult and attach a file to it
    #
    USAGE = """
    Usage: py create_tcr_and_attachment.py <TestCaseFormatedID> <filename>
    """
    #################################################################################################
    
    import sys, os
    import re
    import string
    import base64
    
    from pyral import Rally, rallySettings
    
    #################################################################################################
    
    errout = sys.stderr.write
    
    ATTACHMENT_ATTRIBUTES = ['oid', 'ObjectID', '_type', '_ref', '_CreatedAt', 'Name',
                             'CreationDate', 'Description', 
                             'Content', 'ContentType', 'Size', 
                             'Subscription', 
                             'Workspace',
                             'Artifact', 
                             'User'
                            ] 
    
    ATTACHMENT_IMPORTANT_ATTRS = """
        Subscription   ref     (supplied at creation)
        Workspace      ref     (supplied at creation)
    
        Name           STRING      Required    (name of the file, like foo.txt or ThePlan.doc)
        User           ref to User Required   Settable  (User who added the object)
    
        Content        ref to AttachmentContent
        Size           INTEGER     Required
        ContentType    STRING      Required
    
    
        Artifact       ref to Artifact            (optional field)
    
        Description    TEXT        Optional
    
    """
    
    #################################################################################################
    
    def main(args):
        options = [opt for opt in args if opt.startswith('--')]
        args    = [arg for arg in args if arg not in options]
        server = "rally1.rallydev.com"
        user = "[email protected]"
        password = "topsecret"
        workspace = "My Workspace"
        project = "My Project"
        print " ".join(["|%s|" % item for item in [server, user, '********', workspace, project]])
        rally = Rally(server, user, password, workspace=workspace, version="1.43")  # specify the Rally server and credentials
        rally.enableLogging('rally.hist.create_tcr_and_attachment') # name of file you want logging to go to
    
        if len(args) != 2:
            errout('ERROR: You must supply a Test Case FormattedID and an attachment file name')
            errout(USAGE)
            sys.exit(1)
    
        targetTCID, filename = args
    
        me = rally.getUserInfo(username=user).pop(0)
        print "%s user oid: %s" % (user, me.oid)
    
        target_project = rally.getProject()
        target_tc  = rally.get('TestCase', query='FormattedID = %s' % targetTCID, instance=True)    
    
        datestring = "2014-05-01"
    
        tcr_info = {
             "TestCase"     : target_tc.ref,
             "Build"        : "master-91321",
             "Date"         : datestring,
             "Verdict"      : "Pass",
             "Notes"        : "Honeycomb harvest project."
           }
    
        print "Creating Test Case Result ..."
        tcr = rally.put('TestCaseResult', tcr_info)
        print "Created  TCR: %s" % (tcr.oid)
    
        print "Creating AttachmentContent"
    
        if not os.path.exists(filename):
            raise Exception('Named attachment filename: %s not found' % filename)
        if not os.path.isfile(filename):
            raise Exception('Named attachment filename: %s is not a regular file' % filename)
    
        attachment_file_name = os.path.basename(filename)
        attachment_file_size = os.path.getsize(filename)
        if attachment_file_size > 5000000:
            raise Exception('Attachment file size too large, unable to attach to Rally Artifact')
    
        contents = ''
        with open(filename, 'r') as af:
            contents = base64.encodestring(af.read())
    
        # create an AttachmentContent item
        ac = rally.create('AttachmentContent', {"Content" : contents}, project=None)
        if not ac:
            raise RallyRESTAPIError('Unable to create AttachmentContent for %s' % attachment_file_name)
    
        attachment_info = { "Name"              :  attachment_file_name,
                            "Content"           :  ac.ref,       # ref to AttachmentContent
                            "ContentType"       :  "image/jpeg",    
                            "Size"              :  attachment_file_size, # must be size before encoding!!
                            "User"              :  'user/%s' % me.oid,
                            "TestCaseResult"    :  tcr.ref
                          }
    
        # and finally, create the Attachment
        attachment = rally.create('Attachment', attachment_info, project=None)
        if not attachment:
            raise RallyRESTAPIError('Unable to create Attachment for %s' % attachment_file_name)    
    
    
    #################################################################################################
    #################################################################################################
    
    if __name__ == '__main__':
        main(sys.argv[1:])