From 6c35046ab2ba3052c1c621b5b88e0ce2eeb967a4 Mon Sep 17 00:00:00 2001 From: witzig Date: Wed, 10 May 2017 01:40:02 +0200 Subject: [PATCH 1/5] e2e tests (draft) --- .gitignore | 2 + Gruntfile.js | 2 +- app.js | 5 + config/default.toml | 24 +- lib/dbcheck.js | 7 +- package.json | 17 +- services/test-server.js | 75 +- setup/sql/drop.js | 16 +- setup/sql/init.js | 17 +- setup/sql/mailtrain-test.sql | 1023 +++++++++++++++++++++++++ test/e2e/.eslintrc | 11 + test/e2e/README.md | 6 + test/e2e/bin/README.md | 8 + test/e2e/helpers/config.js | 31 + test/e2e/helpers/driver.js | 15 + test/e2e/helpers/exit-unless-test.js | 16 + test/e2e/index.js | 36 + test/e2e/page-objects/flash.js | 25 + test/e2e/page-objects/home.js | 12 + test/e2e/page-objects/page.js | 61 ++ test/e2e/page-objects/subscription.js | 84 ++ test/e2e/page-objects/users.js | 35 + test/e2e/tests/login.js | 57 ++ test/e2e/tests/subscription.js | 101 +++ test/{ => nodeunit}/frontmail-test.js | 0 views/layout.hbs | 2 +- 26 files changed, 1659 insertions(+), 29 deletions(-) create mode 100644 setup/sql/mailtrain-test.sql create mode 100644 test/e2e/.eslintrc create mode 100644 test/e2e/README.md create mode 100644 test/e2e/bin/README.md create mode 100644 test/e2e/helpers/config.js create mode 100644 test/e2e/helpers/driver.js create mode 100644 test/e2e/helpers/exit-unless-test.js create mode 100644 test/e2e/index.js create mode 100644 test/e2e/page-objects/flash.js create mode 100644 test/e2e/page-objects/home.js create mode 100644 test/e2e/page-objects/page.js create mode 100644 test/e2e/page-objects/subscription.js create mode 100644 test/e2e/page-objects/users.js create mode 100644 test/e2e/tests/login.js create mode 100644 test/e2e/tests/subscription.js rename test/{ => nodeunit}/frontmail-test.js (100%) diff --git a/.gitignore b/.gitignore index 710ec373..c7687df8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,10 @@ npm-debug.log .DS_Store config/development.* config/production.* +config/test.* workers/reports/config/development.* workers/reports/config/production.* +workers/reports/config/test.* dump.rdb # generate POT file every time you want to update your PO file diff --git a/Gruntfile.js b/Gruntfile.js index 6ac983cc..daf89982 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,7 +9,7 @@ module.exports = function (grunt) { }, nodeunit: { - all: ['test/**/*-test.js'] + all: ['test/nodeunit/**/*-test.js'] }, jsxgettext: { diff --git a/app.js b/app.js index f8bad1bf..1143df71 100644 --- a/app.js +++ b/app.js @@ -183,6 +183,11 @@ app.use((req, res, next) => { res.locals.customStyles = config.customstyles || []; res.locals.customScripts = config.customscripts || []; + let bodyClasses = []; + app.get('env') === 'test' && bodyClasses.push('page--' + (req.path.substring(1).replace(/\//g, '--') || 'home')); + req.user && bodyClasses.push('logged-in user-' + req.user.username); + res.locals.bodyClass = bodyClasses.join(' '); + settingsModel.list(['ua_code', 'shoutout'], (err, configItems) => { if (err) { return next(err); diff --git a/config/default.toml b/config/default.toml index 64d45fbe..af72e6dd 100644 --- a/config/default.toml +++ b/config/default.toml @@ -110,16 +110,6 @@ host="0.0.0.0" # VERP hostname is in the same domain as the From address. # disablesenderheader=true -[testserver] -# Starts a vanity server that redirects all mail to /dev/null -# Mostly needed for local development -enabled=false -port=5587 -host="0.0.0.0" -username="testuser" -password="testpass" -logger=false - [ldap] # enable to use ldap user backend enabled=false @@ -177,3 +167,17 @@ templates=[["demo", "Demo Template"]] # The bottom line is that if people who are creating report templates or have write access to the DB cannot be trusted, # then it's safer to switch off the reporting functionality below. enabled=false + +[testserver] +# Starts a vanity server that redirects all mail to /dev/null +# Mostly needed for local development +enabled=false +port=5587 +mailboxserverport=3001 +host="0.0.0.0" +username="testuser" +password="testpass" +logger=false + +[seleniumwebdriver] +browser="phantomjs" diff --git a/lib/dbcheck.js b/lib/dbcheck.js index 001723a1..93264800 100644 --- a/lib/dbcheck.js +++ b/lib/dbcheck.js @@ -107,13 +107,14 @@ function getSql(path, data, callback) { if (err) { return callback(err); } - let renderer = Handlebars.compile(source); - return callback(null, renderer(data || {})); + const rendered = data ? Handlebars.compile(source)(data) : source; + return callback(null, rendered); }); } function runInitial(callback) { - let fname = process.env.DB_FROM_START ? 'base.sql' : 'mailtrain.sql'; + let dump = process.env.NODE_ENV === 'test' ? 'mailtrain-test.sql' : 'mailtrain.sql'; + let fname = process.env.DB_FROM_START ? 'base.sql' : dump; let path = pathlib.join(__dirname, '..', 'setup', 'sql', fname); log.info('sql', 'Loading tables from %s', fname); applyUpdate({ diff --git a/package.json b/package.json index 7d82f7cd..ad306cd2 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,17 @@ "test": "grunt", "start": "node index.js", "sqlinit": "node setup/sql/init.js", - "sqldump": "node setup/sql/dump.js | sed -e '/^\\/\\*.*\\*\\/;$/d' -e 's/.[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]./NOW()/g' > setup/sql/mailtrain.sql", + "sqldump": "node setup/sql/dump.js | sed -e '/^\\/\\*.*\\*\\/;$/d' -e 's/.[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]./NOW()/g' > setup/sql/mailtrain${DUMP_NAME_SUFFIX}.sql", "sqldrop": "node setup/sql/drop.js", "sqlgen": "npm run sqldrop && DB_FROM_START=Y npm run sqlinit && npm run sqldump", "langs:hbs": "jsxgettext -L handlebars -k translate -o langs/hbs.pot views/layout.hbs views/index.hbs", "langs:js": "jsxgettext -o languages/js.pot routes/index.js", - "langs": "npm run langs:hbs && npm run langs:js" + "langs": "npm run langs:hbs && npm run langs:js", + "sqldumptest": "NODE_ENV=test DUMP_NAME_SUFFIX=-test npm run sqldump", + "sqlresettest": "NODE_ENV=test npm run sqldrop && NODE_ENV=test npm run sqlinit", + "starttest": "NODE_ENV=test node index.js", + "_e2e": "PATH=$PATH:./node_modules/phantomjs/lib/phantom/bin:./test/e2e/bin NODE_ENV=test ./node_modules/.bin/mocha test/e2e/index.js", + "e2e": "npm run sqlresettest && npm run _e2e" }, "repository": { "type": "git", @@ -26,12 +31,18 @@ "node": ">=5.0.0" }, "devDependencies": { + "babel-eslint": "^7.2.3", + "chai": "^3.5.0", "eslint-config-nodemailer": "^1.0.0", "grunt": "^1.0.1", "grunt-cli": "^1.2.0", "grunt-contrib-nodeunit": "^1.0.0", "grunt-eslint": "^19.0.0", - "jsxgettext-andris": "^0.9.0-patch.1" + "jsxgettext-andris": "^0.9.0-patch.1", + "mailparser": "^2.0.5", + "mocha": "^3.3.0", + "phantomjs": "^2.1.7", + "selenium-webdriver": "^3.4.0" }, "optionalDependencies": { "posix": "^4.1.1" diff --git a/services/test-server.js b/services/test-server.js index 025a16df..1f4777b8 100644 --- a/services/test-server.js +++ b/services/test-server.js @@ -4,12 +4,37 @@ let log = require('npmlog'); let config = require('config'); let crypto = require('crypto'); let humanize = require('humanize'); +let http = require('http'); let SMTPServer = require('smtp-server').SMTPServer; +let simpleParser = require('mailparser').simpleParser; let totalMessages = 0; let received = 0; +let mailstore = { + accounts: {}, + saveMessage(address, message) { + if (!this.accounts[address]) { + this.accounts[address] = []; + } + this.accounts[address].push(message); + }, + getMail(address, callback) { + if (!this.accounts[address] || this.accounts[address].length === 0) { + let err = new Error('No mail for ' + address); + err.status = 404; + return callback(err); + } + simpleParser(this.accounts[address].shift(), (err, mail) => { + if (err) { + return callback(err.message || err); + } + callback(null, mail); + }); + } +}; + // Setup server let server = new SMTPServer({ @@ -74,8 +99,12 @@ let server = new SMTPServer({ // Handle message stream onData: (stream, session, callback) => { let hash = crypto.createHash('md5'); + let message = ''; stream.on('data', chunk => { hash.update(chunk); + if (/^keep/i.test(session.envelope.rcptTo[0].address)) { + message += chunk; + } }); stream.on('end', () => { let err; @@ -84,6 +113,12 @@ let server = new SMTPServer({ err.responseCode = 552; return callback(err); } + + // Store message for e2e tests + if (/^keep/i.test(session.envelope.rcptTo[0].address)) { + mailstore.saveMessage(session.envelope.rcptTo[0].address, message); + } + received++; callback(null, 'Message queued as ' + hash.digest('hex')); // accept the message once the stream is ended }); @@ -94,6 +129,41 @@ server.on('error', err => { log.error('Test SMTP', err.stack); }); +let mailBoxServer = http.createServer((req, res) => { + let renderer = data => ( + '' + data.title + '' + data.body + '' + ); + + let address = req.url.substring(1); + mailstore.getMail(address, (err, mail) => { + if (err) { + let html = renderer({ + title: 'error', + body: err.message || err + }); + res.writeHead(err.status || 500, { 'Content-Type': 'text/html' }); + return res.end(html); + } + + let html = mail.html || renderer({ + title: 'error', + body: 'This mail has no HTML part' + }); + + // https://nodemailer.com/extras/mailparser/#mail-object + delete mail.html; + delete mail.textAsHtml; + delete mail.attachments; + + let script = ''; + html = html.replace(/<\/body\b/i, match => script + match); + html = html.replace(/target="_blank"/g, 'target="_self"'); + + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(html); + }); +}); + module.exports = callback => { if (config.testserver.enabled) { server.listen(config.testserver.port, config.testserver.host, () => { @@ -112,7 +182,10 @@ module.exports = callback => { } }, 60 * 1000); - setImmediate(callback); + mailBoxServer.listen(config.testserver.mailboxserverport, config.testserver.host, () => { + log.info('Test SMTP', 'Mail Box Server listening on port %s', config.testserver.mailboxserverport); + setImmediate(callback); + }); }); } else { setImmediate(callback); diff --git a/setup/sql/drop.js b/setup/sql/drop.js index ba0291d2..3e972fb3 100644 --- a/setup/sql/drop.js +++ b/setup/sql/drop.js @@ -1,17 +1,23 @@ 'use strict'; -if (process.env.NODE_ENV === 'production') { - console.log('This script does not run in production'); // eslint-disable-line no-console - process.exit(1); -} - let config = require('config'); let spawn = require('child_process').spawn; let log = require('npmlog'); let path = require('path'); +let fs = require('fs'); log.level = 'verbose'; +if (process.env.NODE_ENV === 'production') { + log.error('sqldrop', 'This script does not run in production'); + process.exit(1); +} + +if (process.env.NODE_ENV === 'test' && !fs.existsSync(path.join(__dirname, '..', '..', 'config', 'test.toml'))) { + log.error('sqldrop', 'This script only runs in test if config/test.toml (i.e. a dedicated test database) is present'); + process.exit(1); +} + function createDump(callback) { let cmd = spawn(path.join(__dirname, 'drop.sh'), [], { env: { diff --git a/setup/sql/init.js b/setup/sql/init.js index c3a638e3..c654b451 100644 --- a/setup/sql/init.js +++ b/setup/sql/init.js @@ -1,15 +1,22 @@ 'use strict'; -if (process.env.NODE_ENV === 'production') { - console.log('This script does not run in production'); // eslint-disable-line no-console - process.exit(1); -} - let dbcheck = require('../../lib/dbcheck'); let log = require('npmlog'); +let path = require('path'); +let fs = require('fs'); log.level = 'verbose'; +if (process.env.NODE_ENV === 'production') { + log.error('sqlinit', 'This script does not run in production'); + process.exit(1); +} + +if (process.env.NODE_ENV === 'test' && !fs.existsSync(path.join(__dirname, '..', '..', 'config', 'test.toml'))) { + log.error('sqlinit', 'This script only runs in test if config/test.toml (i.e. a dedicated test database) is present'); + process.exit(1); +} + dbcheck(err => { if (err) { log.error('DB', err); diff --git a/setup/sql/mailtrain-test.sql b/setup/sql/mailtrain-test.sql new file mode 100644 index 00000000..8ae97db9 --- /dev/null +++ b/setup/sql/mailtrain-test.sql @@ -0,0 +1,1023 @@ +SET UNIQUE_CHECKS=0; +SET FOREIGN_KEY_CHECKS=0; + +CREATE TABLE `attachments` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `campaign` int(11) unsigned NOT NULL, + `filename` varchar(255) CHARACTER SET utf8mb4 NOT NULL DEFAULT '', + `content_type` varchar(100) CHARACTER SET ascii NOT NULL DEFAULT '', + `content` longblob, + `size` int(11) NOT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `campaign` (`campaign`), + CONSTRAINT `attachments_ibfk_1` FOREIGN KEY (`campaign`) REFERENCES `campaigns` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `blacklist` ( + `email` varchar(191) NOT NULL, + PRIMARY KEY (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `campaign` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `list` int(11) unsigned NOT NULL, + `segment` int(11) unsigned NOT NULL, + `subscription` int(11) unsigned NOT NULL, + `status` tinyint(4) unsigned NOT NULL DEFAULT '0', + `response` varchar(255) DEFAULT NULL, + `response_id` varchar(255) CHARACTER SET ascii DEFAULT NULL, + `updated` timestamp NULL DEFAULT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `list` (`list`,`segment`,`subscription`), + KEY `created` (`created`), + KEY `response_id` (`response_id`), + KEY `status_index` (`status`), + KEY `subscription_index` (`subscription`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `campaign_tracker` ( + `list` int(11) unsigned NOT NULL, + `subscriber` int(11) unsigned NOT NULL, + `link` int(11) NOT NULL, + `ip` varchar(100) CHARACTER SET ascii DEFAULT NULL, + `device_type` varchar(50) DEFAULT NULL, + `country` varchar(2) CHARACTER SET ascii DEFAULT NULL, + `count` int(11) unsigned NOT NULL DEFAULT '1', + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`list`,`subscriber`,`link`), + KEY `created_index` (`created`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `campaigns` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `cid` varchar(255) CHARACTER SET ascii NOT NULL, + `type` tinyint(4) unsigned NOT NULL DEFAULT '1', + `parent` int(11) unsigned DEFAULT NULL, + `name` varchar(255) NOT NULL DEFAULT '', + `description` text, + `list` int(11) unsigned NOT NULL, + `segment` int(11) unsigned DEFAULT NULL, + `template` int(11) unsigned NOT NULL, + `source_url` varchar(255) CHARACTER SET ascii DEFAULT NULL, + `editor_name` varchar(50) DEFAULT '', + `editor_data` longtext, + `last_check` timestamp NULL DEFAULT NULL, + `check_status` varchar(255) DEFAULT NULL, + `from` varchar(255) DEFAULT '', + `address` varchar(255) DEFAULT '', + `reply_to` varchar(255) DEFAULT '', + `subject` varchar(255) DEFAULT '', + `html` longtext, + `html_prepared` longtext, + `text` longtext, + `status` tinyint(4) unsigned NOT NULL DEFAULT '1', + `tracking_disabled` tinyint(4) unsigned NOT NULL DEFAULT '0', + `scheduled` timestamp NULL DEFAULT NULL, + `status_change` timestamp NULL DEFAULT NULL, + `delivered` int(11) unsigned NOT NULL DEFAULT '0', + `blacklisted` int(11) unsigned NOT NULL DEFAULT '0', + `opened` int(11) unsigned NOT NULL DEFAULT '0', + `clicks` int(11) unsigned NOT NULL DEFAULT '0', + `unsubscribed` int(11) unsigned NOT NULL DEFAULT '0', + `bounced` int(1) unsigned NOT NULL DEFAULT '0', + `complained` int(1) unsigned NOT NULL DEFAULT '0', + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `cid` (`cid`), + KEY `name` (`name`(191)), + KEY `status` (`status`), + KEY `schedule_index` (`scheduled`), + KEY `type_index` (`type`), + KEY `parent_index` (`parent`), + KEY `check_index` (`last_check`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `confirmations` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `cid` varchar(255) CHARACTER SET ascii NOT NULL, + `list` int(11) unsigned NOT NULL, + `email` varchar(255) NOT NULL, + `opt_in_ip` varchar(100) DEFAULT NULL, + `data` text NOT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `cid` (`cid`), + KEY `list` (`list`), + CONSTRAINT `confirmations_ibfk_1` FOREIGN KEY (`list`) REFERENCES `lists` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `custom_fields` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `list` int(11) unsigned NOT NULL, + `name` varchar(255) DEFAULT '', + `key` varchar(100) CHARACTER SET ascii NOT NULL, + `default_value` varchar(255) DEFAULT NULL, + `type` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '', + `group` int(11) unsigned DEFAULT NULL, + `group_template` text, + `column` varchar(255) CHARACTER SET ascii DEFAULT NULL, + `visible` tinyint(4) unsigned NOT NULL DEFAULT '1', + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `list` (`list`,`column`), + KEY `list_2` (`list`), + CONSTRAINT `custom_fields_ibfk_1` FOREIGN KEY (`list`) REFERENCES `lists` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `custom_forms` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `list` int(11) unsigned NOT NULL, + `name` varchar(255) DEFAULT '', + `description` text, + `fields_shown_on_subscribe` varchar(255) DEFAULT '', + `fields_shown_on_manage` varchar(255) DEFAULT '', + `layout` longtext, + `form_input_style` longtext, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `list` (`list`), + CONSTRAINT `custom_forms_ibfk_1` FOREIGN KEY (`list`) REFERENCES `lists` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `custom_forms_data` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `form` int(11) unsigned NOT NULL, + `data_key` varchar(255) DEFAULT '', + `data_value` longtext, + PRIMARY KEY (`id`), + KEY `form` (`form`), + CONSTRAINT `custom_forms_data_ibfk_1` FOREIGN KEY (`form`) REFERENCES `custom_forms` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `import_failed` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `import` int(11) unsigned NOT NULL, + `email` varchar(255) NOT NULL DEFAULT '', + `reason` varchar(255) DEFAULT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `import` (`import`), + CONSTRAINT `import_failed_ibfk_1` FOREIGN KEY (`import`) REFERENCES `importer` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `importer` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `list` int(11) unsigned NOT NULL, + `type` tinyint(4) unsigned NOT NULL DEFAULT '1', + `path` varchar(255) NOT NULL DEFAULT '', + `size` int(11) unsigned NOT NULL DEFAULT '0', + `delimiter` varchar(1) CHARACTER SET ascii NOT NULL DEFAULT ',', + `emailcheck` tinyint(4) unsigned NOT NULL DEFAULT '1', + `status` tinyint(4) unsigned NOT NULL DEFAULT '0', + `error` varchar(255) DEFAULT NULL, + `processed` int(11) unsigned NOT NULL DEFAULT '0', + `new` int(11) unsigned NOT NULL DEFAULT '0', + `failed` int(11) unsigned NOT NULL DEFAULT '0', + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mapping` text NOT NULL, + `finished` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `list` (`list`), + CONSTRAINT `importer_ibfk_1` FOREIGN KEY (`list`) REFERENCES `lists` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `links` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `cid` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '', + `campaign` int(11) unsigned NOT NULL, + `url` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '', + `clicks` int(11) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `cid` (`cid`), + UNIQUE KEY `campaign_2` (`campaign`,`url`), + KEY `campaign` (`campaign`), + CONSTRAINT `links_ibfk_1` FOREIGN KEY (`campaign`) REFERENCES `campaigns` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `lists` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `cid` varchar(255) CHARACTER SET ascii NOT NULL, + `default_form` int(11) unsigned DEFAULT NULL, + `name` varchar(255) NOT NULL DEFAULT '', + `description` text, + `subscribers` int(11) unsigned DEFAULT '0', + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `public_subscribe` tinyint(1) unsigned NOT NULL DEFAULT '1', + PRIMARY KEY (`id`), + UNIQUE KEY `cid` (`cid`), + KEY `name` (`name`(191)) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; +INSERT INTO `lists` (`id`, `cid`, `default_form`, `name`, `description`, `subscribers`, `created`, `public_subscribe`) VALUES (1,'Hkj1vCoJb',NULL,'01 Testlist - Public Subscribe','',0,NOW(),1); +CREATE TABLE `queued` ( + `campaign` int(11) unsigned NOT NULL, + `list` int(11) unsigned NOT NULL, + `subscriber` int(11) unsigned NOT NULL, + `source` varchar(255) DEFAULT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`campaign`,`list`,`subscriber`), + KEY `created` (`created`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `report_templates` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT '', + `mime_type` varchar(255) NOT NULL DEFAULT 'text/html', + `description` text, + `user_fields` longtext, + `js` longtext, + `hbs` longtext, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `reports` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT '', + `description` text, + `report_template` int(11) unsigned NOT NULL, + `params` longtext, + `state` int(11) unsigned NOT NULL DEFAULT '0', + `last_run` datetime DEFAULT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `report_template` (`report_template`), + CONSTRAINT `report_template_ibfk_1` FOREIGN KEY (`report_template`) REFERENCES `report_templates` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `rss` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `parent` int(11) unsigned NOT NULL, + `guid` varchar(255) NOT NULL DEFAULT '', + `pubdate` timestamp NULL DEFAULT NULL, + `campaign` int(11) unsigned DEFAULT NULL, + `found` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `parent_2` (`parent`,`guid`), + KEY `parent` (`parent`), + CONSTRAINT `rss_ibfk_1` FOREIGN KEY (`parent`) REFERENCES `campaigns` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `segment_rules` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `segment` int(11) unsigned NOT NULL, + `column` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '', + `value` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + KEY `segment` (`segment`), + CONSTRAINT `segment_rules_ibfk_1` FOREIGN KEY (`segment`) REFERENCES `segments` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `segments` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `list` int(11) unsigned NOT NULL, + `name` varchar(255) NOT NULL DEFAULT '', + `type` tinyint(4) unsigned NOT NULL, + `created` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `list` (`list`), + KEY `name` (`name`(191)), + CONSTRAINT `segments_ibfk_1` FOREIGN KEY (`list`) REFERENCES `lists` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `settings` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `key` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '', + `value` text NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key` (`key`) +) ENGINE=InnoDB AUTO_INCREMENT=112 DEFAULT CHARSET=utf8mb4; +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (1,'smtp_hostname','localhost'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (2,'smtp_port','5587'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (3,'smtp_encryption','NONE'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (4,'smtp_user','testuser'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (5,'smtp_pass','testpass'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (6,'service_url','http://localhost:3000/'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (7,'admin_email','admin@example.com'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (8,'smtp_max_connections','5'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (9,'smtp_max_messages','100'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (10,'smtp_log',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (11,'default_sender','My Awesome Company'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (12,'default_postaddress','1234 Main Street'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (13,'default_from','My Awesome Company'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (14,'default_address','admin@example.com'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (15,'default_subject','Test message'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (16,'default_homepage','https://mailtrain.org'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (17,'db_schema_version','27'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (46,'ua_code',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (47,'shoutout',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (54,'mail_transport','smtp'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (60,'ses_key',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (61,'ses_secret',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (62,'ses_region','us-east-1'); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (65,'smtp_throttling',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (66,'pgp_passphrase',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (67,'pgp_private_key',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (68,'dkim_api_key',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (69,'dkim_domain',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (70,'dkim_selector',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (71,'dkim_private_key',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (73,'smtp_self_signed',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (74,'smtp_disable_auth',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (75,'verp_use',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (76,'disable_wysiwyg',''); +INSERT INTO `settings` (`id`, `key`, `value`) VALUES (77,'disable_confirmations',''); +CREATE TABLE `subscription` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `cid` varchar(255) CHARACTER SET ascii NOT NULL, + `email` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '', + `opt_in_ip` varchar(100) DEFAULT NULL, + `opt_in_country` varchar(2) DEFAULT NULL, + `tz` varchar(100) CHARACTER SET ascii DEFAULT NULL, + `imported` int(11) unsigned DEFAULT NULL, + `status` tinyint(4) unsigned NOT NULL DEFAULT '1', + `is_test` tinyint(4) unsigned NOT NULL DEFAULT '0', + `status_change` timestamp NULL DEFAULT NULL, + `latest_open` timestamp NULL DEFAULT NULL, + `latest_click` timestamp NULL DEFAULT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `first_name` varchar(255) DEFAULT NULL, + `last_name` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `email` (`email`), + UNIQUE KEY `cid` (`cid`), + KEY `status` (`status`), + KEY `first_name` (`first_name`(191)), + KEY `last_name` (`last_name`(191)), + KEY `subscriber_tz` (`tz`), + KEY `is_test` (`is_test`), + KEY `latest_open` (`latest_open`), + KEY `latest_click` (`latest_click`), + KEY `created` (`created`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `subscription__1` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `cid` varchar(255) CHARACTER SET ascii NOT NULL, + `email` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '', + `opt_in_ip` varchar(100) DEFAULT NULL, + `opt_in_country` varchar(2) DEFAULT NULL, + `tz` varchar(100) CHARACTER SET ascii DEFAULT NULL, + `imported` int(11) unsigned DEFAULT NULL, + `status` tinyint(4) unsigned NOT NULL DEFAULT '1', + `is_test` tinyint(4) unsigned NOT NULL DEFAULT '0', + `status_change` timestamp NULL DEFAULT NULL, + `latest_open` timestamp NULL DEFAULT NULL, + `latest_click` timestamp NULL DEFAULT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `first_name` varchar(255) DEFAULT NULL, + `last_name` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `email` (`email`), + UNIQUE KEY `cid` (`cid`), + KEY `status` (`status`), + KEY `first_name` (`first_name`(191)), + KEY `last_name` (`last_name`(191)), + KEY `subscriber_tz` (`tz`), + KEY `is_test` (`is_test`), + KEY `latest_open` (`latest_open`), + KEY `latest_click` (`latest_click`), + KEY `created` (`created`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `templates` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL DEFAULT '', + `description` text, + `editor_name` varchar(50) DEFAULT '', + `editor_data` longtext, + `html` longtext, + `text` longtext, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `name` (`name`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `trigger` ( + `list` int(11) unsigned NOT NULL, + `subscription` int(11) unsigned NOT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`list`,`subscription`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `triggers` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL DEFAULT '', + `description` text, + `enabled` tinyint(4) unsigned NOT NULL DEFAULT '1', + `list` int(11) unsigned NOT NULL, + `source_campaign` int(11) unsigned DEFAULT NULL, + `rule` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT 'column', + `column` varchar(255) CHARACTER SET ascii DEFAULT NULL, + `seconds` int(11) NOT NULL DEFAULT '0', + `dest_campaign` int(11) unsigned DEFAULT NULL, + `count` int(11) unsigned NOT NULL DEFAULT '0', + `last_check` timestamp NULL DEFAULT NULL, + `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `name` (`name`(191)), + KEY `source_campaign` (`source_campaign`), + KEY `dest_campaign` (`dest_campaign`), + KEY `list` (`list`), + KEY `column` (`column`), + KEY `active` (`enabled`), + KEY `last_check` (`last_check`), + CONSTRAINT `triggers_ibfk_1` FOREIGN KEY (`list`) REFERENCES `lists` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `tzoffset` ( + `tz` varchar(100) NOT NULL DEFAULT '', + `offset` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`tz`) +) ENGINE=InnoDB DEFAULT CHARSET=ascii; +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/abidjan',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/accra',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/addis_ababa',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/algiers',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/asmara',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/asmera',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/bamako',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/bangui',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/banjul',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/bissau',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/blantyre',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/brazzaville',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/bujumbura',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/cairo',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/casablanca',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/ceuta',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/conakry',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/dakar',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/dar_es_salaam',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/djibouti',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/douala',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/el_aaiun',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/freetown',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/gaborone',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/harare',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/johannesburg',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/juba',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/kampala',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/khartoum',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/kigali',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/kinshasa',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/lagos',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/libreville',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/lome',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/luanda',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/lubumbashi',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/lusaka',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/malabo',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/maputo',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/maseru',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/mbabane',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/mogadishu',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/monrovia',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/nairobi',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/ndjamena',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/niamey',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/nouakchott',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/ouagadougou',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/porto-novo',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/sao_tome',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/timbuktu',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/tripoli',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/tunis',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('africa/windhoek',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/adak',-540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/anchorage',-480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/anguilla',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/antigua',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/araguaina',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/buenos_aires',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/catamarca',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/comodrivadavia',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/cordoba',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/jujuy',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/la_rioja',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/mendoza',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/rio_gallegos',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/salta',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/san_juan',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/san_luis',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/tucuman',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/argentina/ushuaia',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/aruba',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/asuncion',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/atikokan',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/atka',-540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/bahia',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/bahia_banderas',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/barbados',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/belem',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/belize',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/blanc-sablon',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/boa_vista',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/bogota',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/boise',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/buenos_aires',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/cambridge_bay',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/campo_grande',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/cancun',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/caracas',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/catamarca',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/cayenne',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/cayman',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/chicago',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/chihuahua',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/coral_harbour',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/cordoba',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/costa_rica',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/creston',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/cuiaba',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/curacao',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/danmarkshavn',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/dawson',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/dawson_creek',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/denver',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/detroit',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/dominica',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/edmonton',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/eirunepe',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/el_salvador',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/ensenada',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/fortaleza',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/fort_nelson',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/fort_wayne',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/glace_bay',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/godthab',-120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/goose_bay',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/grand_turk',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/grenada',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/guadeloupe',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/guatemala',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/guayaquil',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/guyana',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/halifax',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/havana',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/hermosillo',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/indiana/indianapolis',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/indiana/knox',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/indiana/marengo',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/indiana/petersburg',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/indiana/tell_city',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/indiana/vevay',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/indiana/vincennes',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/indiana/winamac',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/indianapolis',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/inuvik',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/iqaluit',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/jamaica',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/jujuy',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/juneau',-480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/kentucky/louisville',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/kentucky/monticello',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/knox_in',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/kralendijk',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/la_paz',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/lima',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/los_angeles',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/louisville',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/lower_princes',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/maceio',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/managua',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/manaus',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/marigot',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/martinique',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/matamoros',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/mazatlan',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/mendoza',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/menominee',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/merida',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/metlakatla',-480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/mexico_city',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/miquelon',-120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/moncton',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/monterrey',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/montevideo',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/montreal',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/montserrat',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/nassau',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/new_york',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/nipigon',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/nome',-480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/noronha',-120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/north_dakota/beulah',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/north_dakota/center',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/north_dakota/new_salem',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/ojinaga',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/panama',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/pangnirtung',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/paramaribo',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/phoenix',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/port-au-prince',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/porto_acre',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/porto_velho',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/port_of_spain',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/puerto_rico',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/punta_arenas',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/rainy_river',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/rankin_inlet',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/recife',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/regina',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/resolute',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/rio_branco',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/rosario',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/santarem',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/santa_isabel',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/santiago',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/santo_domingo',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/sao_paulo',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/scoresbysund',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/shiprock',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/sitka',-480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/st_barthelemy',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/st_johns',-150); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/st_kitts',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/st_lucia',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/st_thomas',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/st_vincent',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/swift_current',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/tegucigalpa',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/thule',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/thunder_bay',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/tijuana',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/toronto',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/tortola',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/vancouver',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/virgin',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/whitehorse',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/winnipeg',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/yakutat',-480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('america/yellowknife',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/casey',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/davis',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/dumontdurville',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/macquarie',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/mawson',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/mcmurdo',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/palmer',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/rothera',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/south_pole',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/syowa',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/troll',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('antarctica/vostok',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('arctic/longyearbyen',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/aden',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/almaty',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/amman',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/anadyr',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/aqtau',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/aqtobe',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/ashgabat',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/ashkhabad',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/atyrau',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/baghdad',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/bahrain',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/baku',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/bangkok',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/barnaul',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/beirut',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/bishkek',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/brunei',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/calcutta',330); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/chita',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/choibalsan',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/chongqing',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/chungking',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/colombo',330); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/dacca',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/damascus',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/dhaka',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/dili',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/dubai',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/dushanbe',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/famagusta',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/gaza',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/harbin',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/hebron',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/hong_kong',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/hovd',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/ho_chi_minh',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/irkutsk',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/istanbul',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/jakarta',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/jayapura',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/jerusalem',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/kabul',270); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/kamchatka',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/karachi',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/kashgar',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/kathmandu',345); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/katmandu',345); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/khandyga',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/kolkata',330); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/krasnoyarsk',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/kuala_lumpur',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/kuching',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/kuwait',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/macao',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/macau',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/magadan',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/makassar',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/manila',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/muscat',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/nicosia',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/novokuznetsk',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/novosibirsk',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/omsk',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/oral',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/phnom_penh',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/pontianak',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/pyongyang',510); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/qatar',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/qyzylorda',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/rangoon',390); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/riyadh',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/saigon',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/sakhalin',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/samarkand',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/seoul',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/shanghai',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/singapore',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/srednekolymsk',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/taipei',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/tashkent',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/tbilisi',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/tehran',270); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/tel_aviv',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/thimbu',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/thimphu',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/tokyo',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/tomsk',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/ujung_pandang',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/ulaanbaatar',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/ulan_bator',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/urumqi',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/ust-nera',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/vientiane',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/vladivostok',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/yakutsk',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/yangon',390); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/yekaterinburg',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('asia/yerevan',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/azores',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/bermuda',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/canary',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/cape_verde',-60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/faeroe',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/faroe',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/jan_mayen',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/madeira',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/reykjavik',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/south_georgia',-120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/stanley',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('atlantic/st_helena',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/act',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/adelaide',570); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/brisbane',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/broken_hill',570); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/canberra',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/currie',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/darwin',570); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/eucla',525); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/hobart',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/lhi',630); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/lindeman',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/lord_howe',630); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/melbourne',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/north',570); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/nsw',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/perth',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/queensland',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/south',570); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/sydney',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/tasmania',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/victoria',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/west',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('australia/yancowinna',570); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('brazil/acre',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('brazil/denoronha',-120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('brazil/east',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('brazil/west',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('canada/atlantic',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('canada/central',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('canada/east-saskatchewan',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('canada/eastern',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('canada/mountain',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('canada/newfoundland',-150); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('canada/pacific',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('canada/saskatchewan',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('canada/yukon',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('cet',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('chile/continental',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('chile/easterisland',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('cst6cdt',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('cuba',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('eet',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('egypt',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('eire',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('est',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('est5edt',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+0',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+1',-60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+10',-600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+11',-660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+12',-720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+2',-120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+3',-180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+4',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+5',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+6',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+7',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+8',-480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt+9',-540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-0',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-1',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-10',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-11',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-12',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-13',780); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-14',840); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-2',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-3',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-4',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-5',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-6',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-7',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-8',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt-9',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/gmt0',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/greenwich',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/uct',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/universal',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/utc',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('etc/zulu',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/amsterdam',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/andorra',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/astrakhan',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/athens',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/belfast',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/belgrade',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/berlin',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/bratislava',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/brussels',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/bucharest',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/budapest',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/busingen',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/chisinau',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/copenhagen',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/dublin',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/gibraltar',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/guernsey',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/helsinki',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/isle_of_man',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/istanbul',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/jersey',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/kaliningrad',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/kiev',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/kirov',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/lisbon',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/ljubljana',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/london',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/luxembourg',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/madrid',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/malta',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/mariehamn',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/minsk',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/monaco',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/moscow',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/nicosia',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/oslo',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/paris',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/podgorica',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/prague',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/riga',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/rome',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/samara',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/san_marino',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/sarajevo',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/saratov',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/simferopol',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/skopje',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/sofia',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/stockholm',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/tallinn',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/tirane',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/tiraspol',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/ulyanovsk',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/uzhgorod',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/vaduz',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/vatican',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/vienna',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/vilnius',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/volgograd',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/warsaw',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/zagreb',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/zaporozhye',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('europe/zurich',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('gb',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('gb-eire',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('gmt',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('gmt+0',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('gmt-0',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('gmt0',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('greenwich',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('hongkong',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('hst',-600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('iceland',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/antananarivo',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/chagos',360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/christmas',420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/cocos',390); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/comoro',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/kerguelen',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/mahe',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/maldives',300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/mauritius',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/mayotte',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('indian/reunion',240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('iran',270); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('israel',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('jamaica',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('japan',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('kwajalein',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('libya',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('met',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('mexico/bajanorte',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('mexico/bajasur',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('mexico/general',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('mst',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('mst7mdt',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('navajo',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('nz',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('nz-chat',765); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/apia',780); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/auckland',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/bougainville',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/chatham',765); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/chuuk',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/easter',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/efate',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/enderbury',780); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/fakaofo',780); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/fiji',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/funafuti',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/galapagos',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/gambier',-540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/guadalcanal',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/guam',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/honolulu',-600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/johnston',-600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/kiritimati',840); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/kosrae',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/kwajalein',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/majuro',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/marquesas',-570); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/midway',-660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/nauru',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/niue',-660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/norfolk',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/noumea',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/pago_pago',-660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/palau',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/pitcairn',-480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/pohnpei',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/ponape',660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/port_moresby',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/rarotonga',-600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/saipan',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/samoa',-660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/tahiti',-600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/tarawa',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/tongatapu',780); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/truk',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/wake',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/wallis',720); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pacific/yap',600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('poland',120); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('portugal',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('prc',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('pst8pdt',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('roc',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('rok',540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('singapore',480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('turkey',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('uct',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('universal',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/alaska',-480); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/aleutian',-540); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/arizona',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/central',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/east-indiana',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/eastern',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/hawaii',-600); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/indiana-starke',-300); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/michigan',-240); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/mountain',-360); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/pacific',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/pacific-new',-420); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('us/samoa',-660); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('utc',0); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('w-su',180); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('wet',60); +INSERT INTO `tzoffset` (`tz`, `offset`) VALUES ('zulu',0); +CREATE TABLE `users` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(255) NOT NULL DEFAULT '', + `password` varchar(255) NOT NULL DEFAULT '', + `email` varchar(255) CHARACTER SET utf8 DEFAULT NULL, + `access_token` varchar(40) DEFAULT NULL, + `reset_token` varchar(255) CHARACTER SET ascii DEFAULT NULL, + `reset_expire` timestamp NULL DEFAULT NULL, + `created` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `email` (`email`), + KEY `username` (`username`(191)), + KEY `reset` (`reset_token`), + KEY `check_reset` (`username`(191),`reset_token`,`reset_expire`), + KEY `token_index` (`access_token`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; +INSERT INTO `users` (`id`, `username`, `password`, `email`, `access_token`, `reset_token`, `reset_expire`, `created`) VALUES (1,'admin','$2a$10$mzKU71G62evnGB2PvQA4k..Wf9jASk.c7a8zRMHh6qQVjYJ2r/g/K','admin@example.com',NULL,NULL,NULL,NOW()); + +SET UNIQUE_CHECKS=1; +SET FOREIGN_KEY_CHECKS=1; diff --git a/test/e2e/.eslintrc b/test/e2e/.eslintrc new file mode 100644 index 00000000..836bac9a --- /dev/null +++ b/test/e2e/.eslintrc @@ -0,0 +1,11 @@ +{ + "parser": "babel-eslint", + "rules": { + "strict": 0, + "no-invalid-this": 0, + "no-unused-expressions": 0 + }, + "env": { + "mocha": true + } +} diff --git a/test/e2e/README.md b/test/e2e/README.md new file mode 100644 index 00000000..f1147d08 --- /dev/null +++ b/test/e2e/README.md @@ -0,0 +1,6 @@ +Running e2e tests requires Node 7.6 or later and a dedicated test database. + +1. Start Mailtrain with `npm run startest` +2. Start e2e tests with `npm run e2e` + +By default the tests run with `phantomjs`. To use different browsers see `test/e2e/bin/README.md`. diff --git a/test/e2e/bin/README.md b/test/e2e/bin/README.md new file mode 100644 index 00000000..5edbe684 --- /dev/null +++ b/test/e2e/bin/README.md @@ -0,0 +1,8 @@ +This directory serves for custom browser drivers. + +1. https://seleniumhq.github.io/selenium/docs/api/javascript/ +2. Download a driver of your choice and put it into this directory +3. chmod +x driver +4. Edit config/test.toml + +Current Firefox issue (and patch): https://github.com/mozilla/geckodriver/issues/683 diff --git a/test/e2e/helpers/config.js b/test/e2e/helpers/config.js new file mode 100644 index 00000000..71726ff7 --- /dev/null +++ b/test/e2e/helpers/config.js @@ -0,0 +1,31 @@ +'use strict'; + +const config = require('config'); + +module.exports = { + app: config, + baseUrl: 'http://localhost:' + config.www.port, + users: { + admin: { + username: 'admin', + password: 'test' + } + }, + lists: { + one: { + id: 1, + cid: 'Hkj1vCoJb', + publicSubscribe: 1, + unsubscriptionMode: 0 + } + }, + settings: { + 'service-url' : 'http://localhost:' + config.www.port + '/', + 'default-homepage': 'https://mailtrain.org', + 'smtp-hostname': config.testserver.host, + 'smtp-port': config.testserver.port, + 'smtp-encryption': 'NONE', + 'smtp-user': config.testserver.username, + 'smtp-pass': config.testserver.password + } +}; diff --git a/test/e2e/helpers/driver.js b/test/e2e/helpers/driver.js new file mode 100644 index 00000000..a9b8444b --- /dev/null +++ b/test/e2e/helpers/driver.js @@ -0,0 +1,15 @@ +'use strict'; + +const config = require('./config'); +const webdriver = require('selenium-webdriver'); + +const driver = new webdriver.Builder() + .forBrowser(config.app.seleniumwebdriver.browser || 'phantomjs') + .build(); + +if (global.USE_SHARED_DRIVER === true) { + driver.originalQuit = driver.quit; + driver.quit = () => {}; +} + +module.exports = driver; diff --git a/test/e2e/helpers/exit-unless-test.js b/test/e2e/helpers/exit-unless-test.js new file mode 100644 index 00000000..e53b2eed --- /dev/null +++ b/test/e2e/helpers/exit-unless-test.js @@ -0,0 +1,16 @@ +'use strict'; + +const config = require('./config'); +const log = require('npmlog'); +const path = require('path'); +const fs = require('fs'); + +if (process.env.NODE_ENV !== 'test' || !fs.existsSync(path.join(__dirname, '..', '..', '..', 'config', 'test.toml'))) { + log.error('e2e', 'This script only runs in test and config/test.toml (i.e. a dedicated test database) is present'); + process.exit(1); +} + +if (config.app.testserver.enabled !== true) { + log.error('e2e', 'This script only runs if the testserver is enabled. Check config/test.toml'); + process.exit(1); +} diff --git a/test/e2e/index.js b/test/e2e/index.js new file mode 100644 index 00000000..d78f5819 --- /dev/null +++ b/test/e2e/index.js @@ -0,0 +1,36 @@ +'use strict'; + +require('./helpers/exit-unless-test'); + +global.USE_SHARED_DRIVER = true; + +const driver = require('./helpers/driver'); +const only = 'only'; +const skip = 'skip'; + + + +let tests = [ + ['tests/login'], + ['tests/subscription'] +]; + + + +tests = tests.filter(t => t[1] !== skip); + +if (tests.some(t => t[1] === only)) { + tests = tests.filter(t => t[1] === only); +} + +describe('e2e', function() { + this.timeout(10000); + + tests.forEach(t => { + describe(t[0], () => { + require('./' + t[0]); // eslint-disable-line global-require + }); + }); + + after(() => driver.originalQuit()); +}); diff --git a/test/e2e/page-objects/flash.js b/test/e2e/page-objects/flash.js new file mode 100644 index 00000000..2fd7d8da --- /dev/null +++ b/test/e2e/page-objects/flash.js @@ -0,0 +1,25 @@ +'use strict'; + +const Page = require('./page'); +let flash; + +class Flash extends Page { + getText() { + return this.element('alert').getText(); + } + clear() { + return this.driver.executeScript(` + var elements = document.getElementsByClassName('alert'); + while(elements.length > 0){ + elements[0].parentNode.removeChild(elements[0]); + } + `); + } +} + +module.exports = driver => flash || new Flash(driver, { + elementToWaitFor: 'alert', + elements: { + alert: 'div.alert:not(.js-warning)' + } +}); diff --git a/test/e2e/page-objects/home.js b/test/e2e/page-objects/home.js new file mode 100644 index 00000000..72ad84a7 --- /dev/null +++ b/test/e2e/page-objects/home.js @@ -0,0 +1,12 @@ +'use strict'; + +const Page = require('./page'); +let home; + +module.exports = driver => home || new Page(driver, { + url: '/', + elementToWaitFor: 'body', + elements: { + body: 'body.page--home' + } +}); diff --git a/test/e2e/page-objects/page.js b/test/e2e/page-objects/page.js new file mode 100644 index 00000000..16c2a6cf --- /dev/null +++ b/test/e2e/page-objects/page.js @@ -0,0 +1,61 @@ +'use strict'; + +const config = require('../helpers/config'); +const webdriver = require('selenium-webdriver'); +const By = webdriver.By; +const until = webdriver.until; + +class Page { + constructor(driver, props) { + this.driver = driver; + this.props = props || { + elements: {} + }; + } + + element(key) { + return this.driver.findElement(By.css(this.props.elements[key] || key)); + } + + navigate() { + this.driver.navigate().to(config.baseUrl + this.props.url); + return this.waitUntilVisible(); + } + + waitUntilVisible() { + let selector = this.props.elements[this.props.elementToWaitFor]; + if (!selector && this.props.url) { + selector = 'body.page--' + (this.props.url.substring(1).replace(/\//g, '--') || 'home'); + } + return selector ? this.driver.wait(until.elementLocated(By.css(selector))) : this.driver.sleep(1000); + } + + submit() { + return this.element('submitButton').click(); + } + + click(key) { + return this.element(key).click(); + } + + getText(key) { + return this.element(key).getText(); + } + + getValue(key) { + return this.element(key).getAttribute('value'); + } + + setValue(key, value) { + return this.element(key).sendKeys(value); + } + + containsText(str) { + // let text = await driver.findElement({ css: 'body' }).getText(); + return this.driver.executeScript(` + return (document.documentElement.textContent || document.documentElement.innerText).indexOf('${str}') > -1; + `); + } +} + +module.exports = Page; diff --git a/test/e2e/page-objects/subscription.js b/test/e2e/page-objects/subscription.js new file mode 100644 index 00000000..858dfdf7 --- /dev/null +++ b/test/e2e/page-objects/subscription.js @@ -0,0 +1,84 @@ +'use strict'; + +const config = require('../helpers/config'); +const Page = require('./page'); + +class Web extends Page { + enterEmail(value) { + this.element('emailInput').clear(); + return this.element('emailInput').sendKeys(value); + } +} + +class Mail extends Page { + navigate(address) { + this.driver.sleep(100); + this.driver.navigate().to(`http://localhost:${config.app.testserver.mailboxserverport}/${address}`); + return this.waitUntilVisible(); + } +} + +module.exports = (driver, list) => ({ + + webSubscribe: new Web(driver, { + url: `/subscription/${list.cid}`, + elementToWaitFor: 'form', + elements: { + form: `form[action="/subscription/${list.cid}/subscribe"]`, + emailInput: '#main-form input[name="email"]', + submitButton: 'a[href="#submit"]' + } + }), + + webConfirmSubscriptionNotice: new Web(driver, { + url: `/subscription/${list.cid}/confirm-notice`, + elementToWaitFor: 'homepageButton', + elements: { + homepageButton: `a[href="${config.settings['default-homepage']}"]` + } + }), + + mailConfirmSubscription: new Mail(driver, { + elementToWaitFor: 'confirmLink', + elements: { + confirmLink: `a[href^="${config.settings['service-url']}subscription/subscribe/"]` + } + }), + + webSubscribedNotice: new Web(driver, { + elementToWaitFor: 'homepageButton', + elements: { + homepageButton: 'a[href^="https://mailtrain.org"]' + } + }), + + mailSubscriptionConfirmed: new Mail(driver, { + elementToWaitFor: 'unsubscribeLink', + elements: { + unsubscribeLink: 'a[href*="/unsubscribe/"]', + manageLink: 'a[href*="/manage/"]' + } + }), + + webUnsubscribe: new Web(driver, { + elementToWaitFor: 'submitButton', + elements: { + submitButton: 'a[href="#submit"]' + } + }), + + webUnsubscribedNotice: new Web(driver, { + elementToWaitFor: 'homepageButton', + elements: { + homepageButton: 'a[href^="https://mailtrain.org"]' + } + }), + + mailUnsubscriptionConfirmed: new Mail(driver, { + elementToWaitFor: 'resubscribeLink', + elements: { + resubscribeLink: `a[href^="${config.settings['service-url']}subscription/${list.cid}"]` + } + }) + +}); diff --git a/test/e2e/page-objects/users.js b/test/e2e/page-objects/users.js new file mode 100644 index 00000000..b2c17b58 --- /dev/null +++ b/test/e2e/page-objects/users.js @@ -0,0 +1,35 @@ +'use strict'; + +const Page = require('./page'); + +class Login extends Page { + enterUsername(value) { + // this.element('usernameInput').clear(); + return this.element('usernameInput').sendKeys(value); + } + enterPassword(value) { + return this.element('passwordInput').sendKeys(value); + } +} + +module.exports = driver => ({ + + login: new Login(driver, { + url: '/users/login', + elementToWaitFor: 'submitButton', + elements: { + usernameInput: 'form[action="/users/login"] input[name="username"]', + passwordInput: 'form[action="/users/login"] input[name="password"]', + submitButton: 'form[action="/users/login"] [type=submit]' + } + }), + + account: new Page(driver, { + url: '/users/account', + elementToWaitFor: 'emailInput', + elements: { + emailInput: 'form[action="/users/account"] input[name="email"]' + } + }) + +}); diff --git a/test/e2e/tests/login.js b/test/e2e/tests/login.js new file mode 100644 index 00000000..238b5def --- /dev/null +++ b/test/e2e/tests/login.js @@ -0,0 +1,57 @@ +'use strict'; + +const config = require('../helpers/config'); +const expect = require('chai').expect; +const driver = require('../helpers/driver'); +const home = require('../page-objects/home')(driver); +const flash = require('../page-objects/flash')(driver); +const { + login, + account +} = require('../page-objects/users')(driver); + +describe('login', function() { + this.timeout(10000); + + before(() => driver.manage().deleteAllCookies()); + + it('can access home page', async () => { + await home.navigate(); + }); + + it('can not access restricted content', async () => { + driver.navigate().to(config.baseUrl + '/settings'); + flash.waitUntilVisible(); + expect(await flash.getText()).to.contain('Need to be logged in to access restricted content'); + await flash.clear(); + }); + + it('can not login with false credentials', async () => { + login.enterUsername(config.users.admin.username); + login.enterPassword('invalid'); + login.submit(); + flash.waitUntilVisible(); + expect(await flash.getText()).to.contain('Incorrect username or password'); + await flash.clear(); + }); + + it('can login as admin', async () => { + login.enterUsername(config.users.admin.username); + login.enterPassword(config.users.admin.password); + login.submit(); + flash.waitUntilVisible(); + expect(await flash.getText()).to.contain('Logged in as admin'); + }); + + it('can access account page as admin', async () => { + await account.navigate(); + }); + + it('can logout', async () => { + driver.navigate().to(config.baseUrl + '/users/logout'); + flash.waitUntilVisible(); + expect(await flash.getText()).to.contain('logged out'); + }); + + after(() => driver.quit()); +}); diff --git a/test/e2e/tests/subscription.js b/test/e2e/tests/subscription.js new file mode 100644 index 00000000..3360c738 --- /dev/null +++ b/test/e2e/tests/subscription.js @@ -0,0 +1,101 @@ +'use strict'; + +const config = require('../helpers/config'); +const shortid = require('shortid'); +const expect = require('chai').expect; +const driver = require('../helpers/driver'); +const Page = require('../page-objects/page'); + +const page = new Page(driver); +const flash = require('../page-objects/flash')(driver); +const { + webSubscribe, + webConfirmSubscriptionNotice, + mailConfirmSubscription, + webSubscribedNotice, + mailSubscriptionConfirmed, + webUnsubscribe, + webUnsubscribedNotice, + mailUnsubscriptionConfirmed +} = require('../page-objects/subscription')(driver, config.lists.one); + +const testuser = { + email: 'keep.' + shortid.generate() + '@mailtrain.org' +}; + +// console.log(testuser.email); + +describe('subscribe (list one)', function() { + this.timeout(10000); + + before(() => driver.manage().deleteAllCookies()); + + it('visits web-subscribe', async () => { + await webSubscribe.navigate(); + }); + + it('submits invalid email (error)', async () => { + webSubscribe.enterEmail('foo@bar.nope'); + webSubscribe.submit(); + flash.waitUntilVisible(); + expect(await flash.getText()).to.contain('Invalid email address'); + }); + + it('submits valid email', async () => { + webSubscribe.enterEmail(testuser.email); + await webSubscribe.submit(); + }); + + it('sees web-confirm-subscription-notice', async () => { + webConfirmSubscriptionNotice.waitUntilVisible(); + expect(await page.containsText('Almost Finished')).to.be.true; + }); + + it('receives mail-confirm-subscription', async () => { + mailConfirmSubscription.navigate(testuser.email); + expect(await page.containsText('Please Confirm Subscription')).to.be.true; + }); + + it('clicks confirm subscription', async () => { + await mailConfirmSubscription.click('confirmLink'); + }); + + it('sees web-subscribed-notice', async () => { + webSubscribedNotice.waitUntilVisible(); + expect(await page.containsText('Subscription Confirmed')).to.be.true; + }); + + it('receives mail-subscription-confirmed', async () => { + mailSubscriptionConfirmed.navigate(testuser.email); + expect(await page.containsText('Subscription Confirmed')).to.be.true; + }); +}); + +describe('unsubscribe (list one)', function() { + this.timeout(10000); + + it('clicks unsubscribe', async () => { + await mailSubscriptionConfirmed.click('unsubscribeLink'); + }); + + it('sees web-unsubscribe', async () => { + webUnsubscribe.waitUntilVisible(); + expect(await page.containsText('Unsubscribe')).to.be.true; + }); + + it('clicks confirm unsubscription', async () => { + await webUnsubscribe.submit(); + }); + + it('sees web-unsubscribed-notice', async () => { + webUnsubscribedNotice.waitUntilVisible(); + expect(await page.containsText('Unsubscribe Successful')).to.be.true; + }); + + it('receives mail-unsubscription-confirmed', async () => { + mailUnsubscriptionConfirmed.navigate(testuser.email); + expect(await page.containsText('You Are Now Unsubscribed')).to.be.true; + }); + + after(() => driver.quit()); +}); diff --git a/test/frontmail-test.js b/test/nodeunit/frontmail-test.js similarity index 100% rename from test/frontmail-test.js rename to test/nodeunit/frontmail-test.js diff --git a/views/layout.hbs b/views/layout.hbs index 339e8006..73eff28a 100644 --- a/views/layout.hbs +++ b/views/layout.hbs @@ -35,7 +35,7 @@ - +