diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 000000000..c83425f06
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,48 @@
+name: CI
+on:
+  pull_request:
+    types: [opened, synchronize]
+jobs:
+  unit_tests:
+    runs-on: ubuntu-latest
+
+    services:
+      # Label used to access the service container
+      postgres:
+        image: postgres
+        env:
+          POSTGRES_PASSWORD: vulnerablecode
+          POSTGRES_DB: vulnerablecode
+        # Set health checks to wait until postgres has started
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+        ports:
+          # Maps tcp port 5432 on service container to the host
+          - 5432:5432
+    steps:
+      - name: Check out repository code
+        uses: actions/checkout@v2
+
+      - name: Set up Python 3.8
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.8
+
+      - name: Install dependencies
+        run: |
+          sudo apt install  python3-dev postgresql libpq-dev build-essential libxml2-dev libxslt1-dev 
+          python -m pip install --upgrade pip 
+          pip install -r requirements.txt
+    
+      - name: Run tests
+        run: pytest
+        env:
+          # The hostname, username used to communicate with the PostgreSQL service container
+          POSTGRES_HOST: localhost
+          VC_DB_USER: postgres
+          POSTGRES_PORT: 5432
+          DJANGO_DEV: 1
+          GH_TOKEN: 1 
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 0714e1815..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-language: python
-python: 3.8
-
-services:
-  - postgresql
-
-install:
-  - pip install -e . -r requirements.txt
-
-env:
-  global:
-  - SECRET_KEY="i1bn=oly)w*2yl-5yc&f!vvgt)p)fh3_2$r#spa!*sw36f5ov7"
-  - GH_TOKEN="dummygithubtoken"
-
-before_script:
-  - psql -c "CREATE DATABASE vulnerablecode;" -U postgres
-  - ./manage.py migrate
-
-script:
-  - ./manage.py collectstatic
-  - pytest
-  - pycodestyle --exclude=migrations,settings.py,venv,lib_oval.py,test_ubuntu.py,test_suse.py,test_data_source.py --max-line-length=100 vulnerablecode vulnerabilities
-
-notifications:
-  email: false
-  webhooks:
-    urls:
-      - https://webhooks.gitter.im/e/b119fa557626081e1f36
-    on_success: change  # options: [always|never|change] default: always
-    on_failure: always  # options: [always|never|change] default: always
-    on_start: never     # options: [always|never|change] default: always
diff --git a/pyproject.toml b/pyproject.toml
index e75f1ceca..78e971219 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,4 +43,5 @@ addopts = [
     "-rfExXw",
     "--strict",
     "--doctest-modules"
-]
\ No newline at end of file
+]
+
diff --git a/requirements.txt b/requirements.txt
index e5d18cdb3..02d8a591e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -42,10 +42,7 @@ pytest-dependency==0.4.0
 pytest-django==3.7.0
 pytest-mock==1.13.0
 python-dateutil==2.8.1
-pytoml==0.1.21
 pytz==2019.3
-PyYAML==5.3.1
-saneyaml==0.4
 schema==0.7.1
 six==1.13.0
 soupsieve==1.9.5
@@ -56,3 +53,5 @@ wcwidth==0.1.7
 whitenoise==5.0.1
 zipp==0.6.0
 requests==2.23.0
+toml==0.10.2
+PyYAML==5.3.1
\ No newline at end of file
diff --git a/vulnerabilities/helpers.py b/vulnerabilities/helpers.py
new file mode 100644
index 000000000..54e0ba60b
--- /dev/null
+++ b/vulnerabilities/helpers.py
@@ -0,0 +1,67 @@
+# Copyright (c) nexB Inc. and others. All rights reserved.
+# http://nexb.com and https://github.com/nexB/vulnerablecode/
+# The VulnerableCode software is licensed under the Apache License version 2.0.
+# Data generated with VulnerableCode require an acknowledgment.
+#
+# You may not use this software except in compliance with the License.
+# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing, software distributed
+# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+# CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+#
+# When you publish or redistribute any data created with VulnerableCode or any VulnerableCode
+# derivative work, you must accompany this data with the following acknowledgment:
+#
+#  Generated with VulnerableCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES
+#  OR CONDITIONS OF ANY KIND, either express or implied. No content created from
+#  VulnerableCode should be considered or used as legal advice. Consult an Attorney
+#  for any legal advice.
+#  VulnerableCode is a free software from nexB Inc. and others.
+#  Visit https://github.com/nexB/vulnerablecode/ for support and download.
+
+import json
+import re
+
+import yaml
+import requests
+import toml
+
+
+def load_yaml(path):
+    with open(path) as f:
+        return yaml.safe_load(f)
+
+
+def load_json(path):
+    with open(path) as f:
+        return json.load(f)
+
+
+def load_toml(path):
+    with open(path) as f:
+        return toml.load(f)
+
+
+def create_etag(data_src, url, etag_key):
+    """
+    Etags are like hashes of web responses. For a data source `data_src`,
+    we maintain (url, etag) mappings in the DB.  `create_etag`  creates
+    (`url`, etag) pair. If a (`url`, etag) already exists then the code
+    skips processing the response further to avoid duplicate work.
+
+    `etag_key` is the name of header which contains the etag for the url.
+    """
+    etag = requests.head(url).headers.get(etag_key)
+    if not etag:
+        return True
+
+    elif url in data_src.config.etags:
+        if data_src.config.etags[url] == etag:
+            return False
+
+    data_src.config.etags[url] = etag
+    return True
+
+
+is_cve = re.compile(r"CVE-\d+-\d+", re.IGNORECASE).match
diff --git a/vulnerabilities/import_runner.py b/vulnerabilities/import_runner.py
index 3eeaeac55..e3355fd55 100644
--- a/vulnerabilities/import_runner.py
+++ b/vulnerabilities/import_runner.py
@@ -90,7 +90,7 @@ class ImportRunner:
     Efficiency:
         - Bulk inserts should be used whenever possible.
         - Checking whether a record already exists should be kept to a minimum
-          (the data source should know this instead).
+        (the data source should know this instead).
         - All update and select operations must use indexed columns.
     """
 
diff --git a/vulnerabilities/importer_yielder.py b/vulnerabilities/importer_yielder.py
index d3567abc5..fc45b82f2 100644
--- a/vulnerabilities/importer_yielder.py
+++ b/vulnerabilities/importer_yielder.py
@@ -188,7 +188,7 @@
         'last_run': None,
         'data_source': 'ProjectKBMSRDataSource',
         'data_source_cfg': {
-            'etag': {}
+            'etags': {}
         }
     },
     {
@@ -216,7 +216,7 @@
         'last_run': None,
         'data_source': 'NginxDataSource',
         'data_source_cfg': {
-            'etag': {}
+            'etags': {}
         },
     },
     {
diff --git a/vulnerabilities/importers/alpine_linux.py b/vulnerabilities/importers/alpine_linux.py
index 78085db26..4197c91c9 100644
--- a/vulnerabilities/importers/alpine_linux.py
+++ b/vulnerabilities/importers/alpine_linux.py
@@ -20,13 +20,13 @@
 #  for any legal advice.
 #  VulnerableCode is a free software code scanning tool from nexB Inc. and others.
 #  Visit https://github.com/nexB/vulnerablecode/ for support and download.
+
 from typing import Any
 from typing import Iterable
 from typing import List
 from typing import Mapping
 from typing import Set
 
-import yaml
 from packageurl import PackageURL
 from schema import Or
 from schema import Regex
@@ -35,6 +35,7 @@
 from vulnerabilities.data_source import Advisory
 from vulnerabilities.data_source import GitDataSource
 from vulnerabilities.data_source import Reference
+from vulnerabilities.helpers import load_yaml
 
 
 def validate_schema(advisory_dict):
@@ -90,19 +91,17 @@ def updated_advisories(self) -> Set[Advisory]:
     def _process_file(self, path) -> List[Advisory]:
         advisories = []
 
-        with open(path) as f:
-            record = yaml.safe_load(f)
-
-            if record["packages"] is None:
-                return advisories
-            validate_schema(record)
+        record = load_yaml(path)
+        if record["packages"] is None:
+            return advisories
+        validate_schema(record)
 
-            for p in record["packages"]:
-                advisories.extend(
-                    self._load_advisories(
-                        p["pkg"], record["distroversion"], record["reponame"], record["archs"],
-                    )
+        for p in record["packages"]:
+            advisories.extend(
+                self._load_advisories(
+                    p["pkg"], record["distroversion"], record["reponame"], record["archs"],
                 )
+            )
 
         return advisories
 
diff --git a/vulnerabilities/importers/apache_httpd.py b/vulnerabilities/importers/apache_httpd.py
index acd06e372..fd3cb5800 100644
--- a/vulnerabilities/importers/apache_httpd.py
+++ b/vulnerabilities/importers/apache_httpd.py
@@ -20,7 +20,7 @@
 #  VulnerableCode is a free software tool from nexB Inc. and others.
 #  Visit https://github.com/nexB/vulnerablecode/ for support and download.
 
-from dataclasses import dataclass
+import dataclasses
 from xml.etree import ElementTree
 
 import requests
@@ -29,9 +29,10 @@
 from vulnerabilities.data_source import Advisory
 from vulnerabilities.data_source import DataSource
 from vulnerabilities.data_source import DataSourceConfiguration
+from vulnerabilities.helpers import create_etag
 
 
-@dataclass
+@dataclasses.dataclass
 class ApacheHTTPDDataSourceConfiguration(DataSourceConfiguration):
     etags: dict
 
@@ -47,25 +48,13 @@ def updated_advisories(self):
         # (url, etag) pair. If a (url, etag) already exists then the code
         # skips processing the response further to avoid duplicate work
 
-        if self.create_etag(self.url):
+        if create_etag(data_src=self, url=self.url, etag_key="ETag"):
             data = fetch_xml(self.url)
             advisories = to_advisories(data)
             return self.batch_advisories(advisories)
 
         return []
 
-    def create_etag(self, url):
-        etag = requests.head(url).headers.get("ETag")
-        if not etag:
-            return True
-
-        elif url in self.config.etags:
-            if self.config.etags[url] == etag:
-                return False
-
-        self.config.etags[url] = etag
-        return True
-
 
 def to_advisories(data):
     advisories = []
diff --git a/vulnerabilities/importers/debian_oval.py b/vulnerabilities/importers/debian_oval.py
index db07305e0..e73a1da26 100644
--- a/vulnerabilities/importers/debian_oval.py
+++ b/vulnerabilities/importers/debian_oval.py
@@ -35,6 +35,7 @@
 
 from vulnerabilities.data_source import OvalDataSource, DataSourceConfiguration
 from vulnerabilities.package_managers import DebianVersionAPI
+from vulnerabilities.helpers import create_etag
 
 
 @dataclasses.dataclass
@@ -52,35 +53,22 @@ def __init__(self, *args, **kwargs):
         # we could avoid setting translations, and have it
         # set by default in the OvalParser, but we don't yet know
         # whether all OVAL providers use the same format
-        self.translations = {'less than': '<'}
+        self.translations = {"less than": "<"}
         self.pkg_manager_api = DebianVersionAPI()
 
     def _fetch(self):
-        base_url = 'https://www.debian.org/security/oval/'
-        file_name = 'oval-definitions-{}.xml'
         releases = self.config.releases
         for release in releases:
-            file_url = base_url + file_name.format(release)
-            if not self.create_etag(file_url):
+            file_url = f"https://www.debian.org/security/oval/oval-definitions-{release}.xml"
+            if not create_etag(data_src=self, url=file_url, etag_key="ETag"):
                 continue
+
             resp = requests.get(file_url).content
             yield (
-                {'type': 'deb', 'namespace': 'debian',
-                    'qualifiers': {'distro': release}
-                 },
-                ET.ElementTree(ET.fromstring(resp.decode('utf-8')))
+                {"type": "deb", "namespace": "debian", "qualifiers": {"distro": release}},
+                ET.ElementTree(ET.fromstring(resp.decode("utf-8"))),
             )
         return []
 
     def set_api(self, packages):
         asyncio.run(self.pkg_manager_api.load_api(packages))
-
-    def create_etag(self, url):
-        etag = requests.head(url).headers.get('ETag')
-        if not etag:
-            return True
-        elif url in self.config.etags:
-            if self.config.etags[url] == etag:
-                return False
-        self.config.etags[url] = etag
-        return True
diff --git a/vulnerabilities/importers/kaybee.py b/vulnerabilities/importers/kaybee.py
index c317d52ce..29b4d2546 100644
--- a/vulnerabilities/importers/kaybee.py
+++ b/vulnerabilities/importers/kaybee.py
@@ -20,13 +20,12 @@
 #  VulnerableCode is a free software tool from nexB Inc. and others.
 #  Visit https://github.com/nexB/vulnerablecode/ for support and download.
 
-import yaml
-
 from packageurl import PackageURL
 
 from vulnerabilities.data_source import GitDataSource
 from vulnerabilities.data_source import Advisory
 from vulnerabilities.data_source import Reference
+from vulnerabilities.helpers import load_yaml
 
 
 class KaybeeDataSource(GitDataSource):
@@ -36,14 +35,11 @@ def __enter__(self):
             recursive=True,
             file_ext="yaml",
         )
-        print(self._added_files.union(self._updated_files))
 
     def updated_advisories(self):
         advisories = []
         for yaml_file in self._added_files.union(self._updated_files):
-            print(yaml_file)
             advisories.append(yaml_file_to_advisory(yaml_file))
-            print(advisories[-1])
 
         return self.batch_advisories(advisories)
 
@@ -76,9 +72,3 @@ def yaml_file_to_advisory(yaml_path):
         resolved_package_urls=resolved_packages,
         vuln_references=references,
     )
-
-
-# TODO refactor all such  commonly needed helpers into one single module
-def load_yaml(path):
-    with open(path) as f:
-        return yaml.safe_load(f)
diff --git a/vulnerabilities/importers/nginx.py b/vulnerabilities/importers/nginx.py
index 51c151354..0cc1cb7ed 100644
--- a/vulnerabilities/importers/nginx.py
+++ b/vulnerabilities/importers/nginx.py
@@ -1,23 +1,23 @@
-# Copyright (c)  nexB Inc. and others. All rights reserved.
+# Copyright (c) nexB Inc. and others. All rights reserved.
 # http://nexb.com and https://github.com/nexB/vulnerablecode/
-# The VulnerableCode software is licensed under the Apache License version
+# The VulnerableCode software is licensed under the Apache License version 2.0.
 # Data generated with VulnerableCode require an acknowledgment.
 #
 # You may not use this software except in compliance with the License.
-# You may obtain a copy of the License at: http://apache.org/licenses/LICE
-# Unless required by applicable law or agreed to in writing, software dist
-# under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
-# CONDITIONS OF ANY KIND, either express or implied. See the License for t
-# specific language governing permissions and limitations under the Licens
+# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing, software distributed
+# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+# CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
 #
-# When you publish or redistribute any data created with VulnerableCode or
-# derivative work, you must accompany this data with the following acknowl
+# When you publish or redistribute any data created with VulnerableCode or any VulnerableCode
+# derivative work, you must accompany this data with the following acknowledgment:
 #
-#  Generated with VulnerableCode and provided on an 'AS IS' BASIS, WITHOUT
-#  OR CONDITIONS OF ANY KIND, either express or implied. No content create
-#  VulnerableCode should be considered or used as legal advice. Consult an
+#  Generated with VulnerableCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES
+#  OR CONDITIONS OF ANY KIND, either express or implied. No content created from
+#  VulnerableCode should be considered or used as legal advice. Consult an Attorney
 #  for any legal advice.
-#  VulnerableCode is a free software  from nexB Inc. and others.
+#  VulnerableCode is a free software tool from nexB Inc. and others.
 #  Visit https://github.com/nexB/vulnerablecode/ for support and download.
 
 import asyncio
@@ -34,11 +34,12 @@
 from vulnerabilities.data_source import DataSourceConfiguration
 from vulnerabilities.data_source import Reference
 from vulnerabilities.package_managers import GitHubTagsAPI
+from vulnerabilities.helpers import create_etag
 
 
 @dataclasses.dataclass
 class NginxDataSourceConfiguration(DataSourceConfiguration):
-    etag: dict
+    etags: dict
 
 
 class NginxDataSource(DataSource):
@@ -57,24 +58,12 @@ def set_api(self):
 
     def updated_advisories(self):
         advisories = []
-        if self.create_etag():
+        if create_etag(data_src=self, url=self.url, etag_key="ETag"):
             self.set_api()
             data = requests.get(self.url).content
             advisories.extend(self.to_advisories(data))
         return self.batch_advisories(advisories)
 
-    def create_etag(self):
-        etag = requests.head(self.url).headers.get("ETag")
-        if not etag:
-            return True
-
-        elif self.url in self.config.etag:
-            if self.config.etag[self.url] == etag:
-                return False
-
-        self.config.etag[self.url] = etag
-        return True
-
     def to_advisories(self, data):
         advisories = []
         soup = BeautifulSoup(data)
diff --git a/vulnerabilities/importers/npm.py b/vulnerabilities/importers/npm.py
index 3b0ae8df5..591f013e9 100644
--- a/vulnerabilities/importers/npm.py
+++ b/vulnerabilities/importers/npm.py
@@ -1,5 +1,5 @@
 # Author: Navonil Das (@NavonilDas)
-# Copyright (c) 2017 nexB Inc. and others. All rights reserved.
+# Copyright (c) nexB Inc. and others. All rights reserved.
 # http://nexb.com and https://github.com/nexB/vulnerablecode/
 # The VulnerableCode software is licensed under the Apache License version 2.0.
 # Data generated with VulnerableCode require an acknowledgment.
@@ -18,11 +18,10 @@
 #  OR CONDITIONS OF ANY KIND, either express or implied. No content created from
 #  VulnerableCode should be considered or used as legal advice. Consult an Attorney
 #  for any legal advice.
-#  VulnerableCode is a free software code scanning tool from nexB Inc. and others.
+#  VulnerableCode is a free software from nexB Inc. and others.
 #  Visit https://github.com/nexB/vulnerablecode/ for support and download.
 
 import asyncio
-import json
 from typing import Any
 from typing import List
 from typing import Mapping
@@ -40,6 +39,7 @@
 from vulnerabilities.data_source import GitDataSource
 from vulnerabilities.data_source import Reference
 from vulnerabilities.package_managers import NpmVersionAPI
+from vulnerabilities.helpers import load_json
 
 NPM_URL = "https://registry.npmjs.org{}"
 
@@ -121,7 +121,9 @@ def _versions_to_purls(package_name, versions):
 
 
 def categorize_versions(
-    all_versions: Set[str], aff_version_range: str, fixed_version_range: str,
+    all_versions: Set[str],
+    aff_version_range: str,
+    fixed_version_range: str,
 ) -> Tuple[Set[str], Set[str]]:
     """
     Seperate list of affected versions and unaffected versions from all versions
@@ -146,8 +148,3 @@ def categorize_versions(
             aff_ver.add(ver)
 
     return aff_ver, fix_ver
-
-
-def load_json(path):
-    with open(path) as f:
-        return json.load(f)
diff --git a/vulnerabilities/importers/nvd.py b/vulnerabilities/importers/nvd.py
index 155f85447..a4c0370bf 100644
--- a/vulnerabilities/importers/nvd.py
+++ b/vulnerabilities/importers/nvd.py
@@ -32,6 +32,7 @@
 from vulnerabilities.data_source import DataSource
 from vulnerabilities.data_source import DataSourceConfiguration
 from vulnerabilities.data_source import Reference
+from vulnerabilities.helpers import create_etag
 
 
 @dataclasses.dataclass
@@ -49,13 +50,13 @@ class NVDDataSource(DataSource):
     def updated_advisories(self):
         current_year = date.today().year
         # NVD json feeds start from 2002.
-        for year in range(2002, current_year+1):
+        for year in range(2002, current_year + 1):
             download_url = BASE_URL.format(year)
             # Etags are like hashes of web responses. We maintain
             # (url, etag) mappings in the DB. `create_etag`  creates
             # (url, etag) pair. If a (url, etag) already exists then the code
             # skips processing the response further to avoid duplicate work
-            if self.create_etag(download_url):
+            if create_etag(data_src=self, url=download_url, etag_key="etag"):
                 data = self.fetch(download_url)
                 yield self.to_advisories(data)
 
@@ -130,15 +131,3 @@ def extract_cpes(cve_item):
             for cpe_data in node.get("cpe_match", []):
                 cpes.add(cpe_data["cpe23Uri"])
         return cpes
-
-    def create_etag(self, url):
-        etag = requests.head(url).headers.get("etag")
-        if not etag:
-            # Kind of inaccurate to return True since etag is
-            # not created
-            return True
-        elif url in self.config.etags:
-            if self.config.etags[url] == etag:
-                return False
-        self.config.etags[url] = etag
-        return True
diff --git a/vulnerabilities/importers/openssl.py b/vulnerabilities/importers/openssl.py
index 00862d663..17e368e67 100644
--- a/vulnerabilities/importers/openssl.py
+++ b/vulnerabilities/importers/openssl.py
@@ -32,6 +32,7 @@
 from vulnerabilities.data_source import DataSource
 from vulnerabilities.data_source import Reference
 from vulnerabilities.data_source import DataSourceConfiguration
+from vulnerabilities.helpers import create_etag
 
 
 @dataclasses.dataclass
@@ -49,27 +50,13 @@ def updated_advisories(self) -> Set[Advisory]:
         # (url, etag) mappings in the DB. `create_etag`  creates
         # (url, etag) pair. If a (url, etag) already exists then the code
         # skips processing the response further to avoid duplicate work
-        if self.create_etag(self.url):
+        if create_etag(data_src=self, url=self.url, etag_key="ETag"):
             raw_data = self.fetch()
             advisories = self.to_advisories(raw_data)
             return self.batch_advisories(advisories)
 
         return []
 
-    def create_etag(self, url):
-        etag = requests.head(url).headers.get("ETag")
-        if not etag:
-            # Kind of inaccurate to return True since etag is
-            # not created
-            return True
-
-        elif url in self.config.etags:
-            if self.config.etags[url] == etag:
-                return False
-
-        self.config.etags[url] = etag
-        return True
-
     def fetch(self):
         return requests.get(self.url).content
 
diff --git a/vulnerabilities/importers/project_kb_msr2019.py b/vulnerabilities/importers/project_kb_msr2019.py
index 0cd0b5d78..932bd541e 100644
--- a/vulnerabilities/importers/project_kb_msr2019.py
+++ b/vulnerabilities/importers/project_kb_msr2019.py
@@ -22,12 +22,11 @@
 
 import csv
 import dataclasses
+import re
 import urllib.request
 
 # Reading CSV file from  a url using `requests` is bit too complicated.
-# Use `urllib.request` for that purpose. Need `requests` because making
-# a HEADER request using `urllib.request` is too complicated.
-import requests
+# Use `urllib.request` for that purpose.
 from packageurl import PackageURL
 
 
@@ -35,11 +34,13 @@
 from vulnerabilities.data_source import DataSource
 from vulnerabilities.data_source import Reference
 from vulnerabilities.data_source import DataSourceConfiguration
+from vulnerabilities.helpers import create_etag
+from vulnerabilities.helpers import is_cve
 
 
 @dataclasses.dataclass
 class ProjectKBDataSourceConfiguration(DataSourceConfiguration):
-    etag: dict
+    etags: dict
 
 
 class ProjectKBMSRDataSource(DataSource):
@@ -49,29 +50,13 @@ class ProjectKBMSRDataSource(DataSource):
     url = "https://raw.githubusercontent.com/SAP/project-kb/master/MSR2019/dataset/vulas_db_msr2019_release.csv"  # nopep8
 
     def updated_advisories(self):
-        # etag are like hashes of web responses. We maintain
-        # (url, etag) mappings in the DB. `create_etag`  creates
-        # (url, etag) pair. If a (url, etag) already exists then the code
-        # skips processing the response further to avoid duplicate work
-        if self.create_etag(self.url):
+        if create_etag(data_src=self, url=self.url, etag_key="ETag"):
             raw_data = self.fetch()
             advisories = self.to_advisories(raw_data)
             return self.batch_advisories(advisories)
 
         return []
 
-    def create_etag(self, url):
-        etag = requests.head(url).headers.get("ETag")
-        if not etag:
-            return True
-
-        elif url in self.config.etag:
-            if self.config.etag[url] == etag:
-                return False
-
-        self.config.etag[url] = etag
-        return True
-
     def fetch(self):
         response = urllib.request.urlopen(self.url)
         lines = [l.decode("utf-8") for l in response.readlines()]
@@ -80,15 +65,23 @@ def fetch(self):
     @staticmethod
     def to_advisories(csv_reader):
         # Project KB MSR csv file has no header row
-        advsiories = []
+        advisories = []
         for row in csv_reader:
             vuln_id, proj_home, fix_commit, _ = row
             commit_link = proj_home + "/commit/" + fix_commit
-            advsiories.append(
+
+            if is_cve(vuln_id):
+                reference = Reference(url=commit_link)
+
+            else:
+                reference = Reference(url=commit_link, reference_id=vuln_id)
+                vuln_id = ""
+
+            advisories.append(
                 Advisory(
                     summary="",
                     impacted_package_urls=[],
-                    vuln_references=[Reference(url=commit_link)],
+                    vuln_references=[reference],
                     cve_id=vuln_id,
                 )
             )
diff --git a/vulnerabilities/importers/ruby.py b/vulnerabilities/importers/ruby.py
index e46c8dc66..cbc893617 100644
--- a/vulnerabilities/importers/ruby.py
+++ b/vulnerabilities/importers/ruby.py
@@ -27,12 +27,12 @@
 from dephell_specifier import RangeSpecifier
 from dephell_specifier.range_specifier import InvalidSpecifier
 from packageurl import PackageURL
-import yaml
 
 from vulnerabilities.data_source import Advisory
 from vulnerabilities.data_source import GitDataSource
 from vulnerabilities.data_source import Reference
 from vulnerabilities.package_managers import RubyVersionAPI
+from vulnerabilities.helpers import load_yaml
 
 
 class RubyDataSource(GitDataSource):
@@ -152,8 +152,3 @@ def categorize_versions(all_versions, unaffected_version_ranges):
                     safe_versions.add(i)
 
         return (safe_versions, all_versions - safe_versions)
-
-
-def load_yaml(path):
-    with open(path) as f:
-        return yaml.safe_load(f)
diff --git a/vulnerabilities/importers/rust.py b/vulnerabilities/importers/rust.py
index f2a3e9997..13e184bc5 100644
--- a/vulnerabilities/importers/rust.py
+++ b/vulnerabilities/importers/rust.py
@@ -21,7 +21,6 @@
 #  Visit https://github.com/nexB/vulnerablecode/ for support and download.
 
 import asyncio
-import json
 from itertools import chain
 from typing import Optional, Mapping
 from typing import Set
@@ -29,7 +28,6 @@
 from urllib.error import HTTPError
 from urllib.request import urlopen
 
-import pytoml as toml
 from dephell_specifier import RangeSpecifier
 from packageurl import PackageURL
 
@@ -37,6 +35,7 @@
 from vulnerabilities.data_source import GitDataSource
 from vulnerabilities.data_source import Reference
 from vulnerabilities.package_managers import CratesVersionAPI
+from vulnerabilities.helpers import load_toml
 
 
 class RustDataSource(GitDataSource):
@@ -173,8 +172,3 @@ def categorize_versions(
             unaffected.update(uncategorized_versions)
 
     return unaffected, affected
-
-
-def load_toml(path):
-    with open(path) as f:
-        return toml.load(f)
diff --git a/vulnerabilities/importers/safety_db.py b/vulnerabilities/importers/safety_db.py
index 7873c0138..caaf389f0 100755
--- a/vulnerabilities/importers/safety_db.py
+++ b/vulnerabilities/importers/safety_db.py
@@ -31,12 +31,10 @@
 from typing import Mapping
 from typing import Set
 from typing import Tuple
-from urllib.error import HTTPError
-from urllib.request import urlopen
-import requests
 
 from dephell_specifier import RangeSpecifier
 from packageurl import PackageURL
+import requests
 from schema import Or
 from schema import Regex
 from schema import Schema
@@ -46,6 +44,7 @@
 from vulnerabilities.data_source import DataSourceConfiguration
 from vulnerabilities.data_source import Reference
 from vulnerabilities.package_managers import PypiVersionAPI
+from vulnerabilities.helpers import create_etag
 
 
 def validate_schema(advisory_dict):
@@ -92,9 +91,8 @@ def set_api(self, packages):
         asyncio.run(self._versions.load_api(packages))
 
     def _fetch(self) -> Mapping[str, Any]:
-        if self.create_etag(self.config.url):
-            with urlopen(self.config.url) as response:
-                return json.load(response)
+        if create_etag(data_src=self, url=self.config.url, etag_key="ETag"):
+            return requests.get(self.config.url).json()
 
         return []
 
@@ -137,33 +135,29 @@ def updated_advisories(self) -> Set[Advisory]:
 
         return self.batch_advisories(advisories)
 
-    def create_etag(self, url):
-        etag = requests.head(url).headers.get('ETag')
-        if not etag:
-            # Kind of inaccurate to return True since etag is
-            # not created
-            return True
-        elif url in self.config.etags:
-            if self.config.etags[url] == etag:
-                return False
-        self.config.etags[url] = etag
-        return True
-
 
 def categorize_versions(
-    package_name: str, all_versions: Set[str], version_specs: Iterable[str],
+    package_name: str,
+    all_versions: Set[str],
+    version_ranges: Iterable[str],
 ) -> Tuple[Set[PackageURL], Set[PackageURL]]:
     """
     :return: impacted, resolved purls
     """
     impacted_versions, impacted_purls = set(), set()
-    ranges = [RangeSpecifier(s) for s in version_specs]
+    ranges = [RangeSpecifier(s) for s in version_ranges]
 
     for version in all_versions:
         if any([version in r for r in ranges]):
             impacted_versions.add(version)
 
-            impacted_purls.add(PackageURL(name=package_name, type="pypi", version=version,))
+            impacted_purls.add(
+                PackageURL(
+                    name=package_name,
+                    type="pypi",
+                    version=version,
+                )
+            )
 
     resolved_purls = set()
     for version in all_versions - impacted_versions:
diff --git a/vulnerabilities/importers/suse_backports.py b/vulnerabilities/importers/suse_backports.py
index 1aed9b17f..610ae1b95 100644
--- a/vulnerabilities/importers/suse_backports.py
+++ b/vulnerabilities/importers/suse_backports.py
@@ -30,6 +30,7 @@
 from vulnerabilities.data_source import Advisory
 from vulnerabilities.data_source import DataSource
 from vulnerabilities.data_source import DataSourceConfiguration
+from vulnerabilities.helpers import create_etag
 
 
 @dataclasses.dataclass
@@ -54,23 +55,11 @@ def updated_advisories(self):
         advisories = []
         all_urls = self.get_all_urls_of_backports(self.config.url)
         for url in all_urls:
-            if not self.create_etag(url):
+            if not create_etag(data_src=self, url=url, etag_key="ETag"):
                 continue
             advisories.extend(self.process_file(self._fetch_yaml(url)))
         return self.batch_advisories(advisories)
 
-    def create_etag(self, url):
-        etag = requests.head(url).headers.get('ETag')
-        if not etag:
-            # Kind of inaccurate to return True since etag is
-            # not created
-            return True
-        elif url in self.config.etags:
-            if self.config.etags[url] == etag:
-                return False
-        self.config.etags[url] = etag
-        return True
-
     def _fetch_yaml(self, url):
 
         try:
diff --git a/vulnerabilities/importers/ubuntu.py b/vulnerabilities/importers/ubuntu.py
index c422ee613..90c5bda1a 100644
--- a/vulnerabilities/importers/ubuntu.py
+++ b/vulnerabilities/importers/ubuntu.py
@@ -36,6 +36,7 @@
 
 from vulnerabilities.data_source import OvalDataSource, DataSourceConfiguration
 from vulnerabilities.package_managers import LaunchpadVersionAPI
+from vulnerabilities.helpers import create_etag
 
 
 @dataclasses.dataclass
@@ -53,22 +54,20 @@ def __init__(self, *args, **kwargs):
         # we could avoid setting translations, and have it
         # set by default in the OvalParser, but we don't yet know
         # whether all OVAL providers use the same format
-        self.translations = {'less than': '<'}
+        self.translations = {"less than": "<"}
         self.pkg_manager_api = LaunchpadVersionAPI()
 
     def _fetch(self):
-        base_url = 'https://people.canonical.com/~ubuntu-security/oval/'
-        file_name = 'com.ubuntu.{}.cve.oval.xml.bz2'
         releases = self.config.releases
         for release in releases:
-            file_url = base_url + file_name.format(release)
-            if not self.create_etag(file_url):
+            file_url = f"https://people.canonical.com/~ubuntu-security/oval/com.ubuntu.{release}.cve.oval.xml.bz2"  # nopep8
+            if not create_etag(data_src=self, url=file_url, etag_key="ETag"):
                 continue
             resp = requests.get(file_url)
             extracted = bz2.decompress(resp.content)
             yield (
-                {'type': 'deb', 'namespace': 'ubuntu'},
-                ET.ElementTree(ET.fromstring(extracted.decode('utf-8')))
+                {"type": "deb", "namespace": "ubuntu"},
+                ET.ElementTree(ET.fromstring(extracted.decode("utf-8"))),
             )
         # In case every file is latest, _fetch won't yield anything(due to checking for new etags),
         # this would return None to added_advisories
@@ -78,16 +77,3 @@ def _fetch(self):
 
     def set_api(self, packages):
         asyncio.run(self.pkg_manager_api.load_api(packages))
-
-    def create_etag(self, url):
-
-        etag = requests.head(url).headers.get('ETag')
-        if not etag:
-            # Kind of inaccurate to return True since etag is
-            # not created
-            return True
-        elif url in self.config.etags:
-            if self.config.etags[url] == etag:
-                return False
-        self.config.etags[url] = etag
-        return True
diff --git a/vulnerabilities/importers/ubuntu_usn.py b/vulnerabilities/importers/ubuntu_usn.py
index fe39850ad..ca93d94f7 100644
--- a/vulnerabilities/importers/ubuntu_usn.py
+++ b/vulnerabilities/importers/ubuntu_usn.py
@@ -30,6 +30,7 @@
 from vulnerabilities.data_source import DataSource
 from vulnerabilities.data_source import Advisory
 from vulnerabilities.data_source import Reference
+from vulnerabilities.helpers import create_etag
 
 
 @dataclasses.dataclass
@@ -43,7 +44,7 @@ class UbuntuUSNDataSource(DataSource):
 
     def updated_advisories(self):
         advisories = []
-        if self.create_etag(self.config.db_url):
+        if create_etag(data_src=self, url=self.config.db_url, etag_key="etag"):
             advisories.extend(self.to_advisories(fetch(self.config.db_url)))
 
         return self.batch_advisories(advisories)
diff --git a/vulnerabilities/package_managers.py b/vulnerabilities/package_managers.py
index 220bfb4d5..f48337756 100644
--- a/vulnerabilities/package_managers.py
+++ b/vulnerabilities/package_managers.py
@@ -29,6 +29,7 @@
 
 from aiohttp import ClientSession
 from aiohttp.client_exceptions import ClientResponseError
+from aiohttp.client_exceptions import ServerDisconnectedError
 
 
 class VersionAPI:
@@ -319,5 +320,4 @@ async def fetch(self, owner_repo: str, session) -> None:
         endpoint = f"https://api.github.com/repos/{owner_repo}/git/refs/tags"
         resp = await session.request(method="GET", url=endpoint)
         resp = await resp.json()
-        print(resp)
         self.cache[owner_repo] = [release["ref"].split("/")[-1] for release in resp]
diff --git a/vulnerabilities/tests/test_helpers.py b/vulnerabilities/tests/test_helpers.py
new file mode 100644
index 000000000..994d6659e
--- /dev/null
+++ b/vulnerabilities/tests/test_helpers.py
@@ -0,0 +1,62 @@
+# Copyright (c) nexB Inc. and others. All rights reserved.
+# http://nexb.com and https://github.com/nexB/vulnerablecode/
+# The VulnerableCode software is licensed under the Apache License version 2.0.
+# Data generated with VulnerableCode require an acknowledgment.
+#
+# You may not use this software except in compliance with the License.
+# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing, software distributed
+# under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR
+# CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+#
+# When you publish or redistribute any data created with VulnerableCode or any VulnerableCode
+# derivative work, you must accompany this data with the following acknowledgment:
+#
+#  Generated with VulnerableCode and provided on an 'AS IS' BASIS, WITHOUT WARRANTIES
+#  OR CONDITIONS OF ANY KIND, either express or implied. No content created from
+#  VulnerableCode should be considered or used as legal advice. Consult an Attorney
+#  for any legal advice.
+#  VulnerableCode is a free software tool from nexB Inc. and others.
+#  Visit https://github.com/nexB/vulnerablecode/ for support and download.
+
+import dataclasses
+from unittest import TestCase
+from unittest.mock import patch
+from unittest.mock import MagicMock
+
+from vulnerabilities.data_source import DataSource
+from vulnerabilities.helpers import create_etag
+
+
+@dataclasses.dataclass
+class DummyDataSourceConfiguration:
+    etags: dict
+
+
+class DummyDataSource(DataSource):
+    CONFIG_CLASS = DummyDataSourceConfiguration
+
+
+class TestHelpers(TestCase):
+    @classmethod
+    def setUpClass(cls):
+        data_source_cfg = {"etags": {}}
+        cls.data_source = DummyDataSource(batch_size=1, config=data_source_cfg)
+
+    def test_create_etag(self):
+        assert self.data_source.config.etags == {}
+
+        mock_response = MagicMock()
+        mock_response.headers = {"ETag": "0x1234"}
+
+        with patch("vulnerabilities.helpers.requests.head", return_value=mock_response):
+            assert (
+                create_etag(data_src=self.data_source, url="https://example.org", etag_key="ETag")
+                is True
+            )
+            assert self.data_source.config.etags == {"https://example.org": "0x1234"}
+            assert (
+                create_etag(data_src=self.data_source, url="https://example.org", etag_key="ETag")
+                is False
+            )
diff --git a/vulnerabilities/tests/test_ubuntu.py b/vulnerabilities/tests/test_ubuntu.py
index 736bc6dc4..caa584a45 100644
--- a/vulnerabilities/tests/test_ubuntu.py
+++ b/vulnerabilities/tests/test_ubuntu.py
@@ -17,10 +17,6 @@
 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 TEST_DATA = os.path.join(BASE_DIR, "test_data/")
 
-class MockResponse:
-
-    headers = {"ETag":"0x1234"}
-
 class TestUbuntuOvalParser(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
@@ -275,15 +271,4 @@ def test_get_data_from_xml_doc(self, mock_write):
             data = {i for i in self.ubuntu_data_src.get_data_from_xml_doc(xml_doc,{"type":"deb"})}
         for adv in data : 
             adv.vuln_references = sorted(adv.vuln_references, key=lambda x : x.url)
-        assert expected_data == data
-
-    def test_create_etag(self):
-        
-        assert self.ubuntu_data_src.config.etags == {}
-        with patch('vulnerabilities.importers.ubuntu.requests.head', return_value=MockResponse()):
-            assert True == self.ubuntu_data_src.create_etag("https://example.org")
-            assert self.ubuntu_data_src.config.etags == {"https://example.org":"0x1234"}
-            assert False == self.ubuntu_data_src.create_etag("https://example.org")
-        
-
-
+        assert expected_data == data
\ No newline at end of file
diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py
index fd02b5023..175fd9b39 100644
--- a/vulnerabilities/views.py
+++ b/vulnerabilities/views.py
@@ -186,10 +186,10 @@ def get(self, request, *args, **kwargs):
         return render(request, self.template_name, context=context)
 
     def post(self, request, *args, **kwargs):
-        if "cve_id" in self.request.POST:
+        if "vuln_id" in self.request.POST:
             is_vulnerable = "impacted" in self.request.headers["Referer"]
             relation = self.create_relationship_instance(
-                cve_id=self.request.POST["cve_id"],
+                cve_id=self.request.POST["vuln_id"],
                 package_id=kwargs["pid"],
                 is_vulnerable=is_vulnerable,
             )
