Skip to content
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 2.1.4

* Bug fix (#41) that `github.get_contents` action would be failed when decode parameter is set,
and fix encoding processing problem in `github.create_file` and `github.update_file` actions.

## 2.1.3

* Fix `update_branch_protection` action: dismissal users and teams can now be null in GitHub's API response.
Expand Down
6 changes: 4 additions & 2 deletions actions/create_file.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import base64

from lib.base import BaseGithubAction
from lib.formatters import file_response_to_dict, decode_base64
from lib.formatters import file_response_to_dict
from lib.utils import prep_github_params_for_file_ops

__all__ = [
Expand All @@ -13,7 +15,7 @@ def run(self, user, repo, path, message, content, branch=None, committer=None, a
author, branch, committer = prep_github_params_for_file_ops(author, branch, committer)

if encoding and encoding == 'base64':
content = decode_base64(content)
content = base64.b64encode(content.encode('utf-8'))

user = self._client.get_user(user)
repo = user.get_repo(repo)
Expand Down
26 changes: 2 additions & 24 deletions actions/lib/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
'user_to_dict',
'contents_to_dict',
'file_response_to_dict',
'decode_base64'
]


Expand Down Expand Up @@ -211,12 +210,8 @@ def contents_to_dict(contents, decode=False):
elif item_type == 'submodule':
data['submodule_git_url'] = item.submodule_git_url
elif not directory:
encoding = item.encoding
content = item.content
data['encoding'] = encoding
if decode and encoding == 'base64':
content = decode_base64(content)
data['content'] = content
data['encoding'] = item.encoding
data['content'] = item.decoded_content.decode('utf-8') if decode else item.content

data['size'] = item.size
data['name'] = item.name
Expand All @@ -234,23 +229,6 @@ def contents_to_dict(contents, decode=False):
return result


def decode_base64(data):
"""Decode base64, padding being optional.

:param data: Base64 data as an ASCII byte string
:returns: The decoded byte string.

"""
missing_padding = len(data) % 4
if missing_padding != 0:
data += b'=' * (4 - missing_padding)

import base64
data = data.encode("utf-8")
data = base64.b64decode(data).decode("utf-8")
return data


def file_response_to_dict(response):
result = {'commit': response['commit'].sha}
return result
6 changes: 4 additions & 2 deletions actions/update_file.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import base64

from lib.base import BaseGithubAction
from lib.formatters import file_response_to_dict, decode_base64
from lib.formatters import file_response_to_dict
from lib.utils import prep_github_params_for_file_ops

__all__ = [
Expand All @@ -13,7 +15,7 @@ def run(self, user, repo, path, message, content, sha, branch=None, committer=No
author, branch, committer = prep_github_params_for_file_ops(author, branch, committer)

if encoding and encoding == 'base64':
content = decode_base64(content)
content = base64.b64encode(content.encode('utf-8'))

user = self._client.get_user(user)
repo = user.get_repo(repo)
Expand Down
2 changes: 1 addition & 1 deletion pack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ keywords:
- git
- scm
- serverless
version: 2.1.3
version: 2.1.4
python_versions:
- "3"
author : StackStorm, Inc.
Expand Down
55 changes: 55 additions & 0 deletions tests/test_action_create_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import base64

from github_base_action_test_case import GitHubBaseActionTestCase
from create_file import CreateFileAction

from github import Github
from unittest.mock import patch
from unittest.mock import Mock


class CreateFileTest(GitHubBaseActionTestCase):
__test__ = True
action_cls = CreateFileAction

def setUp(self):
super(CreateFileTest, self).setUp()

self._test_data = {}
# This is base parameters for running this action
self.action_params = {
'user': 'st2-test',
'repo': 'StackStorm-Test',
'path': 'file-test',
'message': 'commit message',
'content': 'file content',
}

def test_create_file(self):
def _side_effect(*args, **kwargs):
self._test_data['content'] = kwargs['content']
return {'commit': Mock()}

action = self.get_action_instance(self.full_config)

with patch.object(Github, 'get_user', return_value=Mock()) as mock_user:
mock_user.return_value.get_repo.return_value.create_file.side_effect = _side_effect
action.run(**self.action_params)

# This chceks sending content value is expected value
self.assertEqual(self._test_data['content'], self.action_params['content'])

def test_create_file_with_encoding_param(self):
def _side_effect(*args, **kwargs):
self._test_data['content'] = kwargs['content']
return {'commit': Mock()}

action = self.get_action_instance(self.full_config)

with patch.object(Github, 'get_user', return_value=Mock()) as mock_user:
mock_user.return_value.get_repo.return_value.create_file.side_effect = _side_effect
action.run(**dict(self.action_params, **{'encoding': 'base64'}))

# This chceks sending content value is expected value
self.assertEqual(self._test_data['content'],
base64.b64encode(self.action_params['content'].encode('utf-8')))
114 changes: 114 additions & 0 deletions tests/test_action_get_contents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from github_base_action_test_case import GitHubBaseActionTestCase
from get_contents import GetContentsAction

from github import Github
from unittest.mock import patch
from unittest.mock import Mock


class AddCommentActionTestCase(GitHubBaseActionTestCase):
__test__ = True
action_cls = GetContentsAction

def setUp(self):
super(AddCommentActionTestCase, self).setUp()

# This is base parameters for running this action
self.action_params = {
'user': 'st2-test',
'repo': 'StackStorm-Test',
'ref': 'HEAD',
'path': 'file-test',
}

# There are mock data-set of returned contents
_mock_data_base = {
'size': 'test-size',
'name': 'test-name',
'path': 'test-path',
'sha': 'test-sha',
'url': 'https://example.com/test-url',
'git_url': 'https://example.com/test-git_url',
'html_url': 'https://example.com/test-html_url',
'download_url': 'https://example.com/test-download_url',
}
self.mock_data_for_file = dict(_mock_data_base, **{
'type': 'file',
'encoding': 'base64',
'content': 'test-content',
})
self.mock_data_for_symlink = dict(_mock_data_base, **{
'type': 'symlink',
'target': 'test-target',
})
self.mock_data_for_submodule = dict(_mock_data_base, **{
'type': 'submodule',
'submodule_git_url': 'https://example.com/submodule_git_url',
})
self.mock_data_for_directory = dict(_mock_data_base, **{
'type': 'directory',
})

def _confirm_returned_contents(self, action_params, mock_content, expected_content):
"""
This run github.get_contents action and confirm returned value is expected one.
"""
action = self.get_action_instance(self.full_config)

# Configure mocks not to send requests to the GitHub
with patch.object(Github, 'get_user', return_value=Mock()) as mock_user:
mock_user.return_value.get_repo.return_value.get_contents.return_value = mock_content

# Run github.get_contents action with decode parameter
result = action.run(**action_params)

self.assertEqual(result, expected_content)

def test_get_file(self):
mock_content = Mock()
for (key, value) in self.mock_data_for_file.items():
setattr(mock_content, key, value)

# run action and check returned contents
self._confirm_returned_contents(self.action_params, mock_content, self.mock_data_for_file)

def test_get_file_with_decode_param(self):
mock_content = Mock()
for (key, value) in self.mock_data_for_file.items():
setattr(mock_content, key, value)

# This is in case of calling ContentFile.decoded_content
mock_content.decoded_content = b'test-decoded-content'

# run action and check returned contents
params = dict(self.action_params, **{'decode': True})
self._confirm_returned_contents(params, mock_content, dict(self.mock_data_for_file, **{
'content': 'test-decoded-content'
}))

def test_get_directory(self):
mock_content = Mock()
for (key, value) in self.mock_data_for_directory.items():
setattr(mock_content, key, value)

# run action and check returned contents
self._confirm_returned_contents(self.action_params, [mock_content],
[self.mock_data_for_directory])

def test_get_symlink(self):
mock_content = Mock()
for (key, value) in self.mock_data_for_symlink.items():
setattr(mock_content, key, value)

# run action and check returned contents
self._confirm_returned_contents(self.action_params, [mock_content],
[self.mock_data_for_symlink])

def test_get_submodule(self):
mock_content = Mock()
for (key, value) in self.mock_data_for_submodule.items():
setattr(mock_content, key, value)

# run action and check returned contents
self._confirm_returned_contents(self.action_params, [mock_content],
[self.mock_data_for_submodule])
56 changes: 56 additions & 0 deletions tests/test_action_update_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import base64

from github_base_action_test_case import GitHubBaseActionTestCase
from update_file import UpdateFileAction

from github import Github
from unittest.mock import patch
from unittest.mock import Mock


class UpdateFileTest(GitHubBaseActionTestCase):
__test__ = True
action_cls = UpdateFileAction

def setUp(self):
super(UpdateFileTest, self).setUp()

self._test_data = {}
# This is base parameters for running this action
self.action_params = {
'user': 'st2-test',
'repo': 'StackStorm-Test',
'path': 'file-test',
'sha': 'test-key',
'message': 'commit message',
'content': 'file content',
}

def test_update_file(self):
def _side_effect(*args, **kwargs):
self._test_data['content'] = kwargs['content']
return {'commit': Mock()}

action = self.get_action_instance(self.full_config)

with patch.object(Github, 'get_user', return_value=Mock()) as mock_user:
mock_user.return_value.get_repo.return_value.update_file.side_effect = _side_effect
action.run(**self.action_params)

# This chceks sending content value is expected value
self.assertEqual(self._test_data['content'], self.action_params['content'])

def test_update_file_with_encoding_param(self):
def _side_effect(*args, **kwargs):
self._test_data['content'] = kwargs['content']
return {'commit': Mock()}

action = self.get_action_instance(self.full_config)

with patch.object(Github, 'get_user', return_value=Mock()) as mock_user:
mock_user.return_value.get_repo.return_value.update_file.side_effect = _side_effect
action.run(**dict(self.action_params, **{'encoding': 'base64'}))

# This chceks sending content value is expected value
self.assertEqual(self._test_data['content'],
base64.b64encode(self.action_params['content'].encode('utf-8')))