Files
puppetboard/test/test_app.py
2017-06-10 03:40:13 -04:00

823 lines
24 KiB
Python

import pytest
import json
import os
from datetime import datetime
from puppetboard import app
from pypuppetdb.types import Node, Report
from puppetboard import default_settings
from bs4 import BeautifulSoup
class MockDbQuery(object):
def __init__(self, responses):
self.responses = responses
def get(self, method, **kws):
resp = None
if method in self.responses:
resp = self.responses[method].pop(0)
if 'validate' in resp:
checks = resp['validate']['checks']
resp = resp['validate']['data']
for check in checks:
assert check in kws
expected_value = checks[check]
assert expected_value == kws[check]
return resp
@pytest.fixture
def mock_puppetdb_environments(mocker):
environemnts = [
{'name': 'production'},
{'name': 'staging'}
]
return mocker.patch.object(app.puppetdb, 'environments',
return_value=environemnts)
@pytest.fixture
def mock_puppetdb_default_nodes(mocker):
node_list = [
Node('_', 'node-unreported',
report_timestamp='2013-08-01T09:57:00.000Z',
latest_report_hash='1234567',
catalog_timestamp='2013-08-01T09:57:00.000Z',
facts_timestamp='2013-08-01T09:57:00.000Z',
status_report='unreported'),
Node('_', 'node-changed',
report_timestamp='2013-08-01T09:57:00.000Z',
latest_report_hash='1234567',
catalog_timestamp='2013-08-01T09:57:00.000Z',
facts_timestamp='2013-08-01T09:57:00.000Z',
status_report='changed'),
Node('_', 'node-failed',
report_timestamp='2013-08-01T09:57:00.000Z',
latest_report_hash='1234567',
catalog_timestamp='2013-08-01T09:57:00.000Z',
facts_timestamp='2013-08-01T09:57:00.000Z',
status_report='failed'),
Node('_', 'node-noop',
report_timestamp='2013-08-01T09:57:00.000Z',
latest_report_hash='1234567',
catalog_timestamp='2013-08-01T09:57:00.000Z',
facts_timestamp='2013-08-01T09:57:00.000Z',
status_report='noop'),
Node('_', 'node-unchanged',
report_timestamp='2013-08-01T09:57:00.000Z',
latest_report_hash='1234567',
catalog_timestamp='2013-08-01T09:57:00.000Z',
facts_timestamp='2013-08-01T09:57:00.000Z',
status_report='unchanged'),
]
return mocker.patch.object(app.puppetdb, 'nodes',
return_value=iter(node_list))
@pytest.fixture
def input_data(request):
data_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'data')
data = None
with open('%s/%s' % (data_path, request.function.__name__), "r") as fp:
data = fp.read()
return data
@pytest.fixture
def client():
client = app.app.test_client()
return client
def test_first_test():
assert app is not None, ("%s" % reg.app)
def test_no_env(client, mock_puppetdb_environments):
rv = client.get('/nonexsistenv/')
assert rv.status_code == 404
def test_get_index(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
query_data = {
'nodes': [[{'count': 10}]],
'resources': [[{'count': 40}]],
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/')
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
assert rv.status_code == 200
def test_index_all(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
base_str = 'puppetlabs.puppetdb.population:'
query_data = {
'version': [{'version': '4.2.0'}],
'mbean': [
{
'validate': {
'data': {'Value': '50'},
'checks': {
'path': '%sname=num-nodes' % base_str
}
}
},
{
'validate': {
'data': {'Value': '60'},
'checks': {
'path': '%sname=num-resources' % base_str
}
}
},
{
'validate': {
'data': {'Value': 60.3},
'checks': {
'path': '%sname=avg-resources-per-node' % base_str
}
}
}
]
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/%2A/')
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
vals = soup.find_all('h1',
{"class": "ui header darkblue no-margin-bottom"})
assert len(vals) == 3
assert vals[0].string == '50'
assert vals[1].string == '60'
assert vals[2].string == ' 60'
assert rv.status_code == 200
def test_index_all_older_puppetdb(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
base_str = 'puppetlabs.puppetdb.population:type=default,'
query_data = {
'version': [{'version': '3.2.0'}],
'mbean': [
{
'validate': {
'data': {'Value': '50'},
'checks': {
'path': '%sname=num-nodes' % base_str
}
}
},
{
'validate': {
'data': {'Value': '60'},
'checks': {
'path': '%sname=num-resources' % base_str
}
}
},
{
'validate': {
'data': {'Value': 60.3},
'checks': {
'path': '%sname=avg-resources-per-node' % base_str
}
}
}
]
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/%2A/')
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
vals = soup.find_all('h1',
{"class": "ui header darkblue no-margin-bottom"})
assert len(vals) == 3
assert vals[0].string == '50'
assert vals[1].string == '60'
assert vals[2].string == ' 60'
assert rv.status_code == 200
def test_index_division_by_zero(client, mocker):
mock_puppetdb_environments(mocker)
mock_puppetdb_default_nodes(mocker)
query_data = {
'nodes': [[{'count': 0}]],
'resources': [[{'count': 40}]],
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
vals = soup.find_all('h1',
{"class": "ui header darkblue no-margin-bottom"})
assert len(vals) == 3
assert vals[2].string == '0'
def test_offline_mode(client, mocker):
app.app.config['OFFLINE_MODE'] = True
mock_puppetdb_environments(mocker)
mock_puppetdb_default_nodes(mocker)
query_data = {
'nodes': [[{'count': 10}]],
'resources': [[{'count': 40}]],
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/')
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
for link in soup.find_all('link'):
assert "//" not in link['href']
for script in soup.find_all('script'):
if "src" in script.attrs:
assert "//" not in script['src']
assert rv.status_code == 200
def test_default_node_view(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
rv = client.get('/nodes')
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
for label in ['failed', 'changed', 'unreported', 'noop']:
vals = soup.find_all('a',
{"class": "ui %s label status" % label})
assert len(vals) == 1
assert 'node-%s' % (label) in vals[0].attrs['href']
assert rv.status_code == 200
def test_radiator_view(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
query_data = {
'nodes': [[{'count': 10}]],
'resources': [[{'count': 40}]],
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/radiator')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
assert soup.h1 != 'Not Found'
total = soup.find(class_='total')
assert '10' in total.text
def test_radiator_view_all(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
base_str = 'puppetlabs.puppetdb.population:'
query_data = {
'version': [{'version': '4.2.0'}],
'mbean': [
{
'validate': {
'data': {'Value': '50'},
'checks': {
'path': '%sname=num-nodes' % base_str
}
}
},
{
'validate': {
'data': {'Value': '60'},
'checks': {
'path': '%sname=num-resources' % base_str
}
}
},
{
'validate': {
'data': {'Value': 60.3},
'checks': {
'path': '%sname=avg-resources-per-node' % base_str
}
}
}
]
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/%2A/radiator')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
assert soup.h1 != 'Not Found'
total = soup.find(class_='total')
assert '50' in total.text
def test_radiator_view_all_old_version(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
base_str = 'puppetlabs.puppetdb.population:type=default,'
query_data = {
'version': [{'version': '3.2.0'}],
'mbean': [
{
'validate': {
'data': {'Value': '50'},
'checks': {
'path': '%sname=num-nodes' % base_str
}
}
},
{
'validate': {
'data': {'Value': '60'},
'checks': {
'path': '%sname=num-resources' % base_str
}
}
},
{
'validate': {
'data': {'Value': 60.3},
'checks': {
'path': '%sname=avg-resources-per-node' % base_str
}
}
}
]
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/%2A/radiator')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
assert soup.h1 != 'Not Found'
total = soup.find(class_='total')
assert '50' in total.text
def test_radiator_view_json(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
query_data = {
'nodes': [[{'count': 10}]],
'resources': [[{'count': 40}]],
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/radiator', headers={'Accept': 'application/json'})
assert rv.status_code == 200
json_data = json.loads(rv.data.decode('utf-8'))
assert json_data['unreported'] == 1
assert json_data['noop'] == 1
assert json_data['failed'] == 1
assert json_data['changed'] == 1
assert json_data['unchanged'] == 1
def test_radiator_view_bad_env(client, mocker):
mock_puppetdb_environments(mocker)
mock_puppetdb_default_nodes(mocker)
query_data = {
'nodes': [[{'count': 10}]],
'resources': [[{'count': 40}]],
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/nothere/radiator')
assert rv.status_code == 404
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
assert soup.h1.text == 'Not Found'
def test_radiator_view_division_by_zero(client, mocker):
mock_puppetdb_environments(mocker)
mock_puppetdb_default_nodes(mocker)
query_data = {
'nodes': [[{'count': 0}]],
'resources': [[{'count': 40}]],
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/radiator')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
total = soup.find(class_='total')
assert '0' in total.text
def test_json_report_ok(client, mocker, input_data):
mock_puppetdb_environments(mocker)
mock_puppetdb_default_nodes(mocker)
query_response = json.loads(input_data)
query_data = {
'reports': [
{
'validate': {
'data': query_response[:100],
'checks': {
'limit': 100,
'offset': 0
}
}
}
]
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
app.puppetdb.last_total = 499
rv = client.get('/reports/json')
assert rv.status_code == 200
result_json = json.loads(rv.data.decode('utf-8'))
assert 'data' in result_json
assert len(result_json['data']) == 100
def test_json_daily_reports_chart_ok(client, mocker):
mock_puppetdb_environments(mocker)
mock_puppetdb_default_nodes(mocker)
query_data = {
'reports': [
[{'status': 'changed', 'count': 1}]
for i in range(app.app.config['DAILY_REPORTS_CHART_DAYS'])
]
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/daily_reports_chart.json')
result_json = json.loads(rv.data.decode('utf-8'))
assert 'result' in result_json
assert (len(result_json['result']) ==
app.app.config['DAILY_REPORTS_CHART_DAYS'])
day_format = '%Y-%m-%d'
cur_day = datetime.strptime(result_json['result'][0]['day'], day_format)
for day in result_json['result'][1:]:
next_day = datetime.strptime(day['day'], day_format)
assert cur_day < next_day
cur_day = next_day
assert rv.status_code == 200
def test_catalogs_disabled(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
app.app.config['ENABLE_CATALOG'] = False
rv = client.get('/catalogs')
assert rv.status_code == 403
def test_catalogs_view(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
app.app.config['ENABLE_CATALOG'] = True
rv = client.get('/catalogs')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
def test_catalogs_json(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
app.app.config['ENABLE_CATALOG'] = True
rv = client.get('/catalogs/json')
assert rv.status_code == 200
result_json = json.loads(rv.data.decode('utf-8'))
assert 'data' in result_json
for line in result_json['data']:
assert len(line) == 3
found_status = None
for status in ['failed', 'changed', 'unchanged', 'noop', 'unreported']:
val = BeautifulSoup(line[0], 'html.parser').find_all(
'a', {"href": "/node/node-%s" % status})
if len(val) == 1:
found_status = status
break
assert found_status, 'Line does not match any known status'
val = BeautifulSoup(line[2], 'html.parser').find_all(
'form', {"method": "GET",
"action": "/catalogs/compare/node-%s" % found_status})
assert len(val) == 1
def test_catalogs_json_compare(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
app.app.config['ENABLE_CATALOG'] = True
rv = client.get('/catalogs/compare/node-unreported/json')
assert rv.status_code == 200
result_json = json.loads(rv.data.decode('utf-8'))
assert 'data' in result_json
for line in result_json['data']:
assert len(line) == 3
found_status = None
for status in ['failed', 'changed', 'unchanged', 'noop', 'unreported']:
val = BeautifulSoup(line[0], 'html.parser').find_all(
'a', {"href": "/node/node-%s" % status})
if len(val) == 1:
found_status = status
break
assert found_status, 'Line does not match any known status'
val = BeautifulSoup(line[2], 'html.parser').find_all(
'form', {"method": "GET",
"action": "/catalogs/compare/node-unreported...node-%s" %
found_status})
assert len(val) == 1
def test_facts_view(client, mocker, mock_puppetdb_environments):
query_data = {
'fact-names': [[chr(i) for i in range(ord('a'), ord('z') + 1)]]
}
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/facts')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
searchable = soup.find('div', {'class': 'searchable'})
vals = searchable.find_all('div', {'class': 'column'})
assert len(vals) == 4
def test_fact_view_with_graph(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
rv = client.get('/fact/architecture')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
vals = soup.find_all('div', {"id": "factChart"})
assert len(vals) == 1
def test_fact_view_without_graph(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
rv = client.get('/%2A/fact/augeas')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
vals = soup.find_all('div', {"id": "factChart"})
assert len(vals) == 0
def test_fact_value_view(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
rv = client.get('/fact/architecture/amd64')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
vals = soup.find_all('div', {"id": "factChart"})
assert len(vals) == 0
def test_node_view(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
rv = client.get('/node/node-failed')
assert rv.status_code == 200
soup = BeautifulSoup(rv.data, 'html.parser')
assert soup.title.contents[0] == 'Puppetboard'
vals = soup.find_all('table', {"id": "facts_table"})
assert len(vals) == 1
vals = soup.find_all('table', {"id": "reports_table"})
assert len(vals) == 1
def test_fact_json_with_graph(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
values = ['a', 'b', 'b', 'd', True, 'a\nb']
query_data = {'facts': []}
query_data['facts'].append([])
for i, value in enumerate(values):
query_data['facts'][0].append({
'certname': 'node-%s' % i,
'name': 'architecture',
'value': value,
'environment': 'production'
})
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/fact/architecture/json')
assert rv.status_code == 200
result_json = json.loads(rv.data.decode('utf-8'))
assert 'data' in result_json
assert len(result_json['data']) == 6
for line in result_json['data']:
assert len(line) == 2
assert 'chart' in result_json
assert len(result_json['chart']) == 5
# Test group_by
assert result_json['chart'][0]['value'] == 2
def test_fact_json_without_graph(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
values = ['a', 'b', 'b', 'd']
query_data = {'facts': []}
query_data['facts'].append([])
for i, value in enumerate(values):
query_data['facts'][0].append({
'certname': 'node-%s' % i,
'name': 'architecture',
'value': value,
'environment': 'production'
})
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/%2A/fact/augeas/json')
assert rv.status_code == 200
result_json = json.loads(rv.data.decode('utf-8'))
assert 'data' in result_json
assert len(result_json['data']) == 4
for line in result_json['data']:
assert len(line) == 2
assert 'chart' not in result_json
def test_fact_value_json(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
values = ['a', 'b', 'b', 'd']
query_data = {'facts': []}
query_data['facts'].append([])
for i, value in enumerate(values):
query_data['facts'][0].append({
'certname': 'node-%s' % i,
'name': 'architecture',
'value': value,
'environment': 'production'
})
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/fact/architecture/amd64/json')
assert rv.status_code == 200
result_json = json.loads(rv.data.decode('utf-8'))
assert 'data' in result_json
assert len(result_json['data']) == 4
for line in result_json['data']:
assert len(line) == 1
assert 'chart' not in result_json
def test_node_facts_json(client, mocker,
mock_puppetdb_environments,
mock_puppetdb_default_nodes):
values = ['a', 'b', 'b', 'd']
query_data = {'facts': []}
query_data['facts'].append([])
for i, value in enumerate(values):
query_data['facts'][0].append({
'certname': 'node-failed',
'name': 'fact-%s' % i,
'value': value,
'environment': 'production'
})
dbquery = MockDbQuery(query_data)
mocker.patch.object(app.puppetdb, '_query', side_effect=dbquery.get)
rv = client.get('/node/node-failed/facts/json')
assert rv.status_code == 200
result_json = json.loads(rv.data.decode('utf-8'))
assert 'data' in result_json
assert len(result_json['data']) == 4
for line in result_json['data']:
assert len(line) == 2
assert 'chart' not in result_json