diff --git a/.github/workflows/superLinter.yml b/.github/workflows/superLinter.yml new file mode 100644 index 0000000..f0c28ea --- /dev/null +++ b/.github/workflows/superLinter.yml @@ -0,0 +1,30 @@ +name: Super Linter + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + super-linter: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run Super Linter + uses: github/super-linter@v4 + env: + DEFAULT_BRANCH: main + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Uncomment the following lines to enable linter rules for specific languages + # VALIDATE_ALL_CODEBASE: true + # VALIDATE_JAVASCRIPT_STANDARD: false + # VALIDATE_PYTHON_FLAKE8: true + # VALIDATE_TYPESCRIPT_STANDARD: false + # VALIDATE_JAVA: false + # VALIDATE_DOCKERFILE: true + # VALIDATE_YAML: true + # VALIDATE_BASH: true diff --git a/README.md b/README.md index acd477f..efe3ba3 100644 --- a/README.md +++ b/README.md @@ -157,4 +157,6 @@ Here is a link to understand what the Sync Function can and can not do. 8. **Changes Channel(s) Filter**: Add the channel(s) you want to filter by in the changes operation like this:`CHANGES:bob` . -Works on My Computer - Tested & Certified ;-) \ No newline at end of file +Works on My Computer - Tested & Certified ;-) + +Lets Lint it. \ No newline at end of file diff --git a/example_sync_functions/3.sync_function_run.js b/example_sync_functions/3.sync_function_run.js index 83822c0..23cf20a 100644 --- a/example_sync_functions/3.sync_function_run.js +++ b/example_sync_functions/3.sync_function_run.js @@ -1,6 +1,4 @@ function(doc, oldDoc) { - - let a; try { a = doc._id.split(":"); } catch (error) { @@ -34,6 +32,7 @@ function(doc, oldDoc) { function fieldCheck(elementName) { // Check if elementName exists in the data object, is not null, not empty, and not an integer + // channels can not be integers but they can be strings: 100 BAD , "100" GOOD if (typeof elementName !== 'undefined' && elementName !== null && elementName !== '' && typeof elementName !== 'number') { return true; } else { diff --git a/sg_sync_function_tester.py b/sg_sync_function_tester.py index b29568d..f75d8c9 100644 --- a/sg_sync_function_tester.py +++ b/sg_sync_function_tester.py @@ -2,15 +2,13 @@ import os import requests from requests.auth import HTTPBasicAuth -from datetime import datetime import logging +from datetime import datetime import sys import time - # The WORK class represents the main functionality for interacting with Sync Gateway to test the Sync Function class WORK: - # Default configuration values debug = False sgHost = "http://localhost" sgPort = "4984" @@ -72,7 +70,6 @@ def setupLogging(self): # Store the file handler so it can be closed later self.file_handler = file_handler - # Closes the log file def closeLogFile(self): if hasattr(self, 'file_handler'): @@ -108,7 +105,7 @@ def httpRequest(self, method, url, json_data=None, userName="", password="", ses response.raise_for_status() return response.json() if response.text else None except requests.RequestException as e: - if self.debug == True: + if self.debug: self.logger.error(f"Error in HTTP {method}: {e}") return None @@ -139,7 +136,8 @@ def openJsonFolder(self): rev = None for operation in self.operations: if operation.startswith("SLEEP"): - sleep_time = 1 # Default sleep time + + sleep_time = 1 if ":" in operation: try: sleep_time = int(operation.split(":")[1]) @@ -242,9 +240,6 @@ def openJsonFolder(self): self.logger.error(f"[failed] - [GET_RAW] - [Admin] - Error in HTTP GET_RAW for [{doc_id}] - {str(e)}") self.logger.info(f"[failed] - [GET_RAW] - [Admin] - GET_RAW result for [{doc_id}] - null") - - - if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: python3 sg_sync_function_tester.py ") @@ -253,11 +248,4 @@ def openJsonFolder(self): config_file = sys.argv[1] work = WORK(config_file) work.openJsonFolder() - work.closeLogFile() - - - -''' -HOW TO RUN THE UNIT TEST -python3 -m unittest discover -s test -p "test_sg_sync_function_tester.py" -''' \ No newline at end of file + work.closeLogFile() \ No newline at end of file diff --git a/test/test_sg_sync_function_tester.py b/test/test_sg_sync_function_tester.py index 2c931f1..165ca7b 100644 --- a/test/test_sg_sync_function_tester.py +++ b/test/test_sg_sync_function_tester.py @@ -1,18 +1,18 @@ + import unittest from unittest.mock import patch, MagicMock import json import os import sys -from datetime import datetime from requests.auth import HTTPBasicAuth # Add the root directory to the sys.path to allow imports sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - from sg_sync_function_tester import WORK + class TestWORK(unittest.TestCase): - + def setUp(self): self.config = { "sgHost": "http://localhost", @@ -29,9 +29,12 @@ def setUp(self): "jsonFolder": "jsons", "logPathToWriteTo": "sync_gateway_log", "debug": False, - "operations": ["GET", "PUT", "DELETE", "CHANGES", "GET_ADMIN", "PUT_ADMIN", "DELETE_ADMIN", "CHANGES_ADMIN:bob", "SLEEP:3", "GET_RAW", "PURGE"] + "operations": [ + "GET", "PUT", "DELETE", "CHANGES", "GET_ADMIN", "PUT_ADMIN", + "DELETE_ADMIN", "CHANGES_ADMIN:bob", "SLEEP:3", "GET_RAW", "PURGE" + ] } - + self.config_file = 'test_config.json' with open(self.config_file, 'w') as f: json.dump(self.config, f) @@ -64,7 +67,10 @@ def test_httpRequest_get(self, mock_request): result = self.work.httpRequest("GET", url, userName="bob", password="12345") self.assertEqual(result, self.sample_doc) - mock_request.assert_called_once_with("GET", url, json=None, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth("bob", "12345")) + mock_request.assert_called_once_with( + "GET", url, json=None, headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth("bob", "12345") + ) @patch('requests.request') def test_httpRequest_put(self, mock_request): @@ -75,10 +81,16 @@ def test_httpRequest_put(self, mock_request): mock_request.return_value = mock_response url = f"{self.work.sgHost}:{self.work.sgPort}/{self.work.constructDbUrl()}/foo" - result = self.work.httpRequest("PUT", url, json_data=self.sample_doc, userName="bob", password="12345") + result = self.work.httpRequest( + "PUT", url, json_data=self.sample_doc, userName="bob", password="12345" + ) self.assertEqual(result, {"ok": True, "id": "foo", "rev": "1-a"}) - mock_request.assert_called_once_with("PUT", url, json=self.sample_doc, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth("bob", "12345")) + mock_request.assert_called_once_with( + "PUT", url, json=self.sample_doc, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth("bob", "12345") + ) @patch('requests.request') def test_getChangesFeed(self, mock_request): @@ -95,8 +107,12 @@ def test_getChangesFeed(self, mock_request): result = self.work.getChangesFeed(userName="bob", password="12345", channels="bob") self.assertEqual(result, changes_feed) - url = f"{self.work.sgHost}:{self.work.sgPort}/{self.work.constructDbUrl()}/_changes?filter=sync_gateway/bychannel&channels=bob" - mock_request.assert_called_once_with("GET", url, json=None, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth("bob", "12345")) + url = (f"{self.work.sgHost}:{self.work.sgPort}/{self.work.constructDbUrl()}/" + "_changes?filter=sync_gateway/bychannel&channels=bob") + mock_request.assert_called_once_with( + "GET", url, json=None, headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth("bob", "12345") + ) @patch('requests.request') def test_postPurge(self, mock_request): @@ -112,14 +128,17 @@ def test_postPurge(self, mock_request): self.assertEqual(result, purge_response) url = f"{self.work.sgHost}:{self.work.sgAdminPort}/{self.work.constructDbUrl()}/_purge" purge_data = {"foo": ["*"]} - mock_request.assert_called_once_with("POST", url, json=purge_data, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword)) + mock_request.assert_called_once_with( + "POST", url, json=purge_data, headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword) + ) @patch('requests.request') def test_openJsonFolder(self, mock_request): def side_effect(*args, **kwargs): mock_response = MagicMock() mock_response.raise_for_status = MagicMock() - + if args[0] == "GET": mock_response.json.return_value = {"_id": "foo", "_rev": "1-a", "channels": ["bob"]} elif args[0] == "PUT": @@ -130,7 +149,7 @@ def side_effect(*args, **kwargs): mock_response.json.return_value = {"purged": {"foo": ["*"]}} else: mock_response.json.return_value = {} - + mock_response.text = json.dumps(mock_response.json.return_value) return mock_response @@ -141,18 +160,60 @@ def side_effect(*args, **kwargs): sgDbUrl = f"{self.work.sgHost}:{self.work.sgPort}/{self.work.sgDb}" sgAdminUrl = f"{self.work.sgHost}:{self.work.sgAdminPort}/{self.work.sgDb}" expected_calls = [ - unittest.mock.call("GET", f"{sgDbUrl}/foo", json=None, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth("bob", "12345")), - unittest.mock.call("PUT", f"{sgDbUrl}/foo", json={'_id': 'foo', 'channels': ['bob'], '_rev': '1-a', 'dateTimeStamp': unittest.mock.ANY}, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth("bob", "12345")), - unittest.mock.call("DELETE", f"{sgDbUrl}/foo?rev=1-a", json=None, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth("bob", "12345")), - unittest.mock.call("GET", f"{sgDbUrl}/_changes", json=None, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth("bob", "12345")), - unittest.mock.call("GET", f"{sgAdminUrl}/foo", json=None, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword)), - unittest.mock.call("PUT", f"{sgAdminUrl}/foo", json={'_id': 'foo', 'channels': ['bob'], '_rev': '1-a', 'dateTimeStamp': unittest.mock.ANY}, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword)), - unittest.mock.call("DELETE", f"{sgAdminUrl}/foo?rev=1-a", json=None, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword)), - unittest.mock.call("GET", f"{sgAdminUrl}/_changes?filter=sync_gateway/bychannel&channels=bob", json=None, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword)), - unittest.mock.call("GET", f"{sgAdminUrl}/_raw/foo", json=None, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword)), - unittest.mock.call("POST", f"{sgAdminUrl}/_purge", json={"foo": ["*"]}, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword)) + unittest.mock.call( + "GET", f"{sgDbUrl}/foo", json=None, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth("bob", "12345") + ), + unittest.mock.call( + "PUT", f"{sgDbUrl}/foo", + json={'_id': 'foo', 'channels': ['bob'], '_rev': '1-a', 'dateTimeStamp': unittest.mock.ANY}, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth("bob", "12345") + ), + unittest.mock.call( + "DELETE", f"{sgDbUrl}/foo?rev=1-a", json=None, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth("bob", "12345") + ), + unittest.mock.call( + "GET", f"{sgDbUrl}/_changes", json=None, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth("bob", "12345") + ), + unittest.mock.call( + "GET", f"{sgAdminUrl}/foo", json=None, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword) + ), + unittest.mock.call( + "PUT", f"{sgAdminUrl}/foo", + json={'_id': 'foo', 'channels': ['bob'], '_rev': '1-a', 'dateTimeStamp': unittest.mock.ANY}, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword) + ), + unittest.mock.call( + "DELETE", f"{sgAdminUrl}/foo?rev=1-a", json=None, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword) + ), + unittest.mock.call( + "GET", f"{sgAdminUrl}/_changes?filter=sync_gateway/bychannel&channels=bob", + json=None, headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword) + ), + unittest.mock.call( + "GET", f"{sgAdminUrl}/_raw/foo", json=None, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword) + ), + unittest.mock.call( + "POST", f"{sgAdminUrl}/_purge", json={"foo": ["*"]}, + headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth(self.work.sgAdminUser, self.work.sgAdminPassword) + ) ] - + mock_request.assert_has_calls(expected_calls, any_order=True) def test_constructDbUrl(self): @@ -161,5 +222,6 @@ def test_constructDbUrl(self): self.work.sgDbCollection = "collection1" self.assertEqual(self.work.constructDbUrl(), "sync_gateway.scope1.collection1") + if __name__ == '__main__': unittest.main()