Skip to content

Commit

Permalink
Merge pull request #77 from sandstorm-io/run-unit-tests-on-travis
Browse files Browse the repository at this point in the history
Run unit tests on travis
  • Loading branch information
paulproteus committed Aug 6, 2015
2 parents e197358 + 6011f60 commit ea29e50
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 11 deletions.
9 changes: 8 additions & 1 deletion .travis-run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
set +e # failure OK for now...
set -x

# Run the unit tests, which run via Jasmine+Velocity.
ln -s /etc/sandcats-meteor-settings.json sandcats/dev-settings.json
make action-run-unit-tests

# Now run the full-on integration tests, which do DNS queries and do
# real timeouts so run somewhat slowly at the moment, requiring a
# working nginx setup etc.

pushd /vagrant/sandcats
MAIL_URL=smtp://localhost:2500/ MONGO_URL=mongodb://localhost/sandcats_mongo meteor run --settings /etc/sandcats-meteor-settings.json &
popd
Expand Down Expand Up @@ -34,5 +42,4 @@ sudo service pdns restart
# Restart nginx, in case it is wants to be all 502-y
sudo service nginx restart

# Now, actually run the tests
make action-run-tests
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ before_install:
- sudo rsync -a $PWD/. /vagrant/.
# We run 'meteor' below first to do the user-specific install process...
- meteor --version
# We need Twisted for the silly Python script.
- sudo apt-get --quiet=2 -y install python-twisted
# ...and now we do the actual meteor run command.
script:
- bash .travis-run-tests.sh
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ action-run-dev:
action-run-tests: /usr/share/doc/python-requests /usr/share/doc/python-dnspython /usr/share/doc/python-netifaces /usr/share/doc/python-twisted
cd sandcats && python integration_tests.py

action-run-unit-tests:
(cd sandcats ; tail --retry -f ./.meteor/local/log/jasmine-server-integration.log & (meteor --test --settings=dev-settings.json 2>&1 || true) | python ../meteor-testing-nonsense/input-filter.py )

action-run-unit-tests-continuously:
(cd sandcats ; tail --retry -f ./.meteor/local/log/jasmine-server-integration.log & meteor --test --settings=dev-settings.json )

/srv/sandcats/source/.git: /usr/share/doc/git
sudo mkdir -p /srv/sandcats/source
sudo chown -R vagrant /srv/sandcats/source
Expand Down
48 changes: 48 additions & 0 deletions meteor-testing-nonsense/input-filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/python
import sys
import os

from twisted.internet import stdio, reactor
from twisted.protocols import basic

class Echo(basic.LineReceiver):
from os import linesep as delimiter

def exitWithStatusCode(self):
print 'trying to exit'
if self.all_good:
os._exit(0)
else:
os._exit(1)

def _done(self):
print 'in _done'
os.system('killall -9 node')
reactor.removeAll()
reactor.stop()

def connectionMade(self):
self.all_good = True

def lineReceived(self, line):
self.sendLine(line)

if 'FAIL' in line:
self.all_good = False
self.sendLine("NOTICED A FAILURE!")
reactor.callLater(0.1, self._done)
print 'called later'

if 'time to exit' in line:
reactor.callLater(0.1, self._done)

def main():
e = Echo()
stdio.StandardIO(e)
from twisted.internet import reactor
reactor.run()
e.exitWithStatusCode()


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions sandcats/.meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ aldeed:collection2
email
random
meteorhacks:ssr
sanjo:jasmine
11 changes: 11 additions & 0 deletions sandcats/.meteor/versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
alanning:package-stubber@0.0.9
aldeed:collection2@2.3.1
aldeed:simple-schema@1.3.0
application-configuration@1.0.4
Expand All @@ -9,6 +10,7 @@ blaze-tools@1.0.2
boilerplate-generator@1.0.2
callback-hook@1.0.2
check@1.0.4
coffeescript@1.0.5
copleykj:mesosphere@0.1.14
ddp@1.0.14
deps@1.0.6
Expand Down Expand Up @@ -47,12 +49,16 @@ mrt:underscore-string-latest@2.3.3
npm-container@1.0.0
observe-sequence@1.0.4
ordered-dict@1.0.2
practicalmeteor:chai@1.9.2_3
practicalmeteor:loglevel@1.1.0_3
random@1.0.2
reactive-dict@1.0.5
reactive-var@1.0.4
reload@1.1.2
retry@1.0.2
routepolicy@1.0.4
sanjo:jasmine@0.12.4
sanjo:karma@1.4.1
session@1.0.5
spacebars@1.0.5
spacebars-compiler@1.0.4
Expand All @@ -61,5 +67,10 @@ tracker@1.0.5
ui@1.0.5
underscore@1.0.2
url@1.0.3
velocity:core@0.4.5
velocity:meteor-stubs@1.0.0_2
velocity:node-soft-mirror@0.3.1
velocity:shim@0.1.0
velocity:test-proxy@0.0.4
webapp@1.1.6
webapp-hashing@1.0.2
17 changes: 17 additions & 0 deletions sandcats/exampleuser2.sandcatz.io.csr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICrTCCAZUCAQAwaDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEhMB8GA1UEAwwYZXhh
bXBsZXVzZXIyLnNhbmRjYXR6LmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEArYCEJjYYgpvfSuhfC5qVVn6KZNxfEF4/eobsZy9MggL9cAjNfnoWaMKz
mjwG3QFUX0JhKgLAH2P69OfQIY14A2bvy8INV3ZiuvmUWJa0mn9NiAFWtBi+SJUE
UTMuf1ZmZT6cLRFvmHxLy8DRGDZlhtEbiRSyX+oZZq3rbgtWcyX3lMCKRtpvMrgZ
xLw/9lYxVqdnCMb04n7V6Kk07qequUQUwLPL/Uu/N85HRFKo1Sue/+ISsqgL1YmS
/i9I5C8O3mfwdgzGjj+1lsHXRLxnfUaB5j86GWOreNaEsRv7IpsYWrXQwTxbFSev
Rc0hES5x5WsNGyDIG87PdcEI8Vh+gwIDAQABoAAwDQYJKoZIhvcNAQELBQADggEB
AF5wbFMFUvWSS8jxcdw4ZcgNyzms55eMdxNlDxlTkSOPafRKrU0cg300VSPlQKNz
Tm4XDI3IAeGFxNnZwcobYQ7kisRnKnng7gX6MWzDCwMyVR3TP9US5847pJOP2aRx
xOUndGxEI6QcPgTJRKkTWeK1AIfTLxYkvbwNg69X/bVqrdhszbkRC6VlxiKrypZl
WAfEynAFAg0KUjZFt6UJkzcqVHqllAdxAyWaWjG4KVXgLKKAAy7t9S3wEtn4Qq+K
i4/hRj+vF1XtS1NMs9hEJlk03JZRC5CSP8eak2ba7kcAx2/5WmtJ4bMZL/ByFOZ4
q7QlW9yjdWiiA2+k9PRBGhE=
-----END CERTIFICATE REQUEST-----
11 changes: 6 additions & 5 deletions sandcats/integration_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,9 @@ def reset_app_state():
# We require a restart of the app, if it's in development mode.
os.system('killall -INT node')

os.system('sudo service pdns restart')
time.sleep(1) # Make sure the restart gets a chance to start, to avoid HTTP 502.
os.system('sudo service nginx restart')
os.system('sudo service pdns restart')
# Attempt to get the homepage, which will mean that Meteor is back, waiting at most 10 seconds.
requests.get('http://localhost/', timeout=10)

Expand Down Expand Up @@ -759,14 +759,15 @@ def test_udp_protocol():
client.close()

# Now, make sure that benb3 would not be surprised by messages
# from localhost.
message = 'benb3 0123456789abcdef'
# from localhost. Use a slightly different constant so we know
# we're not being somehow fooled by duplicate messages.
message = 'benb3 abcdef0123456789'
try:
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(message, (UDP_DEST_IP, UDP_DEST_PORT))
client.settimeout(1)
client.recv(1024)
assert False, "We were hoping for no response."
data = client.recv(1024)
assert False, "We were hoping for no response, but got " + repr(data)
except socket.timeout:
# Hooray! No response.
print "."
Expand Down
9 changes: 9 additions & 0 deletions sandcats/lib/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ var settingsSchema = new SimpleSchema({
// kind of "From:" address. Here is where we store that.
EMAIL_FROM_ADDRESS: {
type: String,
},

// When we make API calls to GlobalSign, we need to use a username
// and password. The username is not a secret; the password is. It
// is permissible to run this code without the GlobalSign username
// configured.
GLOBALSIGN_USERNAME: {
type: String,
optional: true
}
});

Expand Down
85 changes: 81 additions & 4 deletions sandcats/lib/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Mesosphere.registerRule('keyFingerprintUnique', function (fieldValue, ruleValue)
return true;
});

// Provide a "public key must be unique" rule, for validating the
// public key. Mesosphere handles making sure it is the right length.
// Add extra constraints about hyphen use: can't start with a hyphen; can't end with
// a hyphen; can't have two hyphens next to each other.
Mesosphere.registerRule('extraHyphenRegexes', function (fieldValue, ruleValue) {
Expand Down Expand Up @@ -53,6 +55,45 @@ Mesosphere.registerAggregate('domainExistsSoCanBeRecovered', function(fields, fo
return !! userRegistration;
});

function commonNameMatchesHostname(csr, rawHostname) {
var commonNameFromCsr = getCommonNameFromCsr(csr);

var baseDomainWithDot = "." + Meteor.settings.BASE_DOMAIN;

// Verify that the hostname ends in our BASE_DOMAIN
if (! commonNameFromCsr.endsWith(baseDomainWithDot)) {
return false;
}

// Remove exactly one reference of that from the end.
var hostnameFromCsr = commonNameFromCsr.slice(
0, commonNameFromCsr.lastIndexOf(baseDomainWithDot));

// This converts the hostname on both sides to lowercase, doing
// a case-insensitive comparison.
if (rawHostname.toLowerCase() === hostnameFromCsr.toLowerCase()) {
return true;
}

return false;
}

Mesosphere.registerAggregate('getCertificateIsAuthorized', function(fields, formFieldsObject) {
var csr = formFieldsObject.certificateSigningRequest;
if (!csr) {
return false;
}

var pubkey = formFieldsObject.pubkey;
var hostname = formFieldsObject.rawHostname;

if (! _hostnameAndPubkeyMatch(pubkey, hostname)) {
return false;
}

return commonNameMatchesHostname(csr, hostname);
});

Mesosphere.registerAggregate('recoveryIsAuthorized', function(fields, formFieldsObject) {
// Recovery is authorized under the following circumstances.
//
Expand Down Expand Up @@ -88,10 +129,7 @@ Mesosphere.registerAggregate('recoveryIsAuthorized', function(fields, formFields
return false;
});

Mesosphere.registerAggregate('hostnameAndPubkeyMatch', function(fields, formFieldsObject) {
var pubkey = formFieldsObject.pubkey;
var hostname = formFieldsObject.rawHostname;

function _hostnameAndPubkeyMatch(pubkey, hostname) {
if (UserRegistrations.findOne({publicKeyId: pubkey,
hostname: hostname})) {
// This means we have a match. Hooray!
Expand All @@ -100,6 +138,13 @@ Mesosphere.registerAggregate('hostnameAndPubkeyMatch', function(fields, formFiel

// By default, do not permit the update.
return false;
}

Mesosphere.registerAggregate('hostnameAndPubkeyMatch', function(fields, formFieldsObject) {
var pubkey = formFieldsObject.pubkey;
var hostname = formFieldsObject.rawHostname;

return _hostnameAndPubkeyMatch(pubkey, hostname);
});

var RECOVERY_TIME_PERIOD_IN_SECONDS = 15 * 60;
Expand Down Expand Up @@ -263,6 +308,9 @@ Mesosphere({
extraHyphenRegexes: true,
}
},
certificateSigningRequest: {
required: false
},
ipAddress: {
required: true,
format: "ipv4",
Expand Down Expand Up @@ -318,6 +366,35 @@ Mesosphere({
}
});

// Create validator for a request that we sign a CSR.
Mesosphere({
name: 'getCertificate',
fields: {
rawHostname: {
required: true,
format: /^[0-9a-zA-Z-]+$/,
transforms: ["clean", "toLowerCase"],
rules: {
minLength: 1,
maxLength: 20
}
},
certificateSigningRequest: {
required: true
},
pubkey: {
required: true,
rules: {
minLength: 40,
maxLength: 40
},
}
},
aggregates: {
isAuthorized: ['getCertificateIsAuthorized', ['pubkey', 'rawHostname', 'certificateSigningRequest']]
}
});

// Create validator for recovery token sending request.
Mesosphere({
name: 'recoverDomainForm',
Expand Down
18 changes: 18 additions & 0 deletions sandcats/lib/x509.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Code that deals with parsing data out of certificates lives here.
var pem = Meteor.npmRequire('pem');
var readCertificateInfo = Meteor.wrapAsync(pem.readCertificateInfo);

getCommonNameFromCsr = function(csrData) {
// readCertificateInfo() can crash if csrData is empty, so
// we work around that here.
if (!csrData) {
return "";
}

try {
return readCertificateInfo(csrData).commonName || "";
} catch (error) {
console.error(error);
return "";
}
};
4 changes: 3 additions & 1 deletion sandcats/packages.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"mysql": "2.5.4"
"mysql": "2.5.4",
"pem": "1.7.2",
"soap": "0.9.1"
}
6 changes: 6 additions & 0 deletions sandcats/sandcats.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ if (Meteor.isServer) {
// Validate that the config file contains the data we need.
validateSettings();

/* In the mirror process for running automated tests, do not do
* PowerDNS setup nor UDP pings setup. */
if (process.env.IS_MIRROR) {
return;
}

// Create our DNS zone for PowerDNS, if necessary.
mysqlQuery = createWrappedQuery();
createDomainIfNeeded(mysqlQuery);
Expand Down
Loading

0 comments on commit ea29e50

Please sign in to comment.