├── test ├── .gitignore ├── replies │ ├── soap │ │ ├── AddBilledJob.ok.xml │ │ ├── AddRecruiterV2.ok.xml │ │ ├── UpdateBilledJob.ok.xml │ │ ├── UpdateRecruiterWithBillingID.ok.xml │ │ ├── GetCategoryTerms.single.xml │ │ ├── CheckRecruiterExistsV2.single.xml │ │ ├── GetCategories.single.xml │ │ ├── GetLocations.single.xml │ │ ├── GetCategories.many.xml │ │ ├── SoapFault.xml │ │ ├── GetCategoryTerms.many.xml │ │ └── GetLocations.many.xml │ └── rest │ │ ├── get.jobinfo.jobid.json │ │ ├── get.jobinfo.search.facets.json │ │ ├── get.employer.search.json │ │ └── get.jobinfo.search.json ├── rest-api-tests.js └── soap-api-tests.js ├── .travis.yml ├── lib ├── parseBooleans.js ├── soap-templates │ ├── GetCategories.hbs │ ├── GetLocations.hbs │ ├── GetCategoryTerms.hbs │ ├── CheckRecruiterExistsV2.hbs │ ├── UpdateBilledJob.hbs │ ├── AddBilledJob.hbs │ ├── AddRecruiterV2.hbs │ └── UpdateRecruiterWithBillingID.hbs ├── util.js ├── soap-api-service-description.json ├── rest-api-service-description.json ├── rest-api-client.js └── soap-api-client.js ├── index.js ├── .gitignore ├── LICENSE ├── package.json └── README.md /test/.gitignore: -------------------------------------------------------------------------------- 1 | service-config.json 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /lib/parseBooleans.js: -------------------------------------------------------------------------------- 1 | // https://github.com/Leonidas-from-XIV/node-xml2js/pull/200 2 | 3 | module.exports = function(str) { 4 | return /^(?:true|false)$/i.test(str) ? str.toLowerCase() === 'true' : str 5 | } -------------------------------------------------------------------------------- /lib/soap-templates/GetCategories.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /lib/soap-templates/GetLocations.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{prefix}} 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/soap-templates/GetCategoryTerms.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{categoryId}} 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/replies/soap/AddBilledJob.ok.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1340 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/replies/soap/AddRecruiterV2.ok.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1339 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/replies/soap/UpdateBilledJob.ok.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1340 5 | 6 | 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('node-madgex') 2 | var utils = require('./lib/util.js') 3 | var restClient = require('./lib/rest-api-client.js') 4 | var soapClient = require('./lib/soap-api-client.js') 5 | 6 | module.exports = { 7 | utils: utils, 8 | createClient: function(baseUrl, credentials) { 9 | return { 10 | restApi: restClient(baseUrl, credentials), 11 | soapApi: soapClient(baseUrl) 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /test/replies/soap/UpdateRecruiterWithBillingID.ok.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1339 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/soap-templates/CheckRecruiterExistsV2.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{recruiterName}} 6 | {{customerBillingId}} 7 | {{madgexID}} 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 2 | var defaultEnvironment = 'development', 3 | serviceUrlSuffix = { 4 | 'development': '-webservice.madgexjbtest.com', 5 | 'production': '-webservice.madgexjb.com', 6 | getCurrent: function () { 7 | return serviceUrlSuffix[process.env.NODE_ENV || defaultEnvironment ] 8 | } 9 | } 10 | 11 | 12 | 13 | module.exports = { 14 | serviceUrlSuffix : serviceUrlSuffix, 15 | getHostName: function (sitename) { 16 | return sitename + module.exports.serviceUrlSuffix.getCurrent(); 17 | } 18 | } -------------------------------------------------------------------------------- /test/replies/soap/GetCategoryTerms.single.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 21 7 | Sport and Leisure 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/replies/soap/CheckRecruiterExistsV2.single.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 11713 7 | 23005869 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/replies/soap/GetCategories.single.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | true 8 | 105 9 | Hours 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/replies/soap/GetLocations.single.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17116 7 | 8 | Longney 9 | GB 10 | 10 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | test/service-config.json 30 | 31 | .ntvs_analysis.dat 32 | 33 | #Visual Studio stuff 34 | 35 | *.sln 36 | 37 | *.suo 38 | 39 | *.njsproj 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 GuideSmiths Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-madgex", 3 | "version": "0.0.6", 4 | "description": "node-madgex", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --recursive test", 8 | "test-debug": "mocha --debug-brk --recursive test", 9 | "prepush": "npm test" 10 | }, 11 | "author": "GuideSmiths ", 12 | "dependencies": { 13 | "change-case": "^2.3.0", 14 | "cheerio": "^0.19.0", 15 | "debug": "^2.1.3", 16 | "extend": "^2.0.0", 17 | "handlebars": "^3.0.3", 18 | "json-pointer": "^0.3.0", 19 | "lodash": "^3.8.0", 20 | "nock": "^1.3.0", 21 | "oauth": "^0.9.12", 22 | "request": "^2.55.0", 23 | "when": "^3.7.2", 24 | "xml2js": "^0.4.8" 25 | }, 26 | "devDependencies": { 27 | "husky": "^0.7.0", 28 | "mocha": "^2.2.4" 29 | }, 30 | "directories": { 31 | "test": "test" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/guidesmiths/node-madgex.git" 36 | }, 37 | "keywords": [ 38 | "madgex", 39 | "client", 40 | "SOAP", 41 | "REST" 42 | ], 43 | "license": "ISC", 44 | "bugs": { 45 | "url": "https://github.com/guidesmiths/node-madgex/issues" 46 | }, 47 | "homepage": "https://github.com/guidesmiths/node-madgex" 48 | } 49 | -------------------------------------------------------------------------------- /lib/soap-api-service-description.json: -------------------------------------------------------------------------------- 1 | { 2 | "billingApi": { 3 | "path": "/billing.asmx", 4 | "services": { 5 | "GetCategories": { 6 | "forceArray": true, 7 | "result": "/soap:Envelope/soap:Body/getCategoriesResponse/getCategoriesResult/wSCategory" 8 | }, 9 | "GetCategoryTerms": { 10 | "forceArray": true, 11 | "result": "/soap:Envelope/soap:Body/getCategoryTermsResponse/getCategoryTermsResult/wSTerm" 12 | }, 13 | "GetLocations": { 14 | "forceArray": true, 15 | "result": "/soap:Envelope/soap:Body/getLocationsResponse/getLocationsResult/wSLocation" 16 | }, 17 | "AddBilledJob": { 18 | "result": "/soap:Envelope/soap:Body/addBilledJobResponse/addBilledJobResult" 19 | }, 20 | "UpdateBilledJob": { 21 | "result": "/soap:Envelope/soap:Body/updateBilledJobResponse/updateBilledJobResult" 22 | }, 23 | "AddRecruiterV2": { 24 | "result": "/soap:Envelope/soap:Body/addRecruiterV2Response/addRecruiterV2Result" 25 | }, 26 | "UpdateRecruiterWithBillingID": { 27 | "result": "/soap:Envelope/soap:Body/updateRecruiterWithBillingIDResponse/updateRecruiterWithBillingIDResult" 28 | }, 29 | "CheckRecruiterExistsV2": { 30 | "result": "/soap:Envelope/soap:Body/checkRecruiterExistsV2Response/checkRecruiterExistsV2Result" 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /test/replies/soap/GetCategories.many.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | false 8 | true 9 | 100 10 | Academic Discipline 11 | 12 | 13 | true 14 | true 15 | 101 16 | Location 17 | 18 | 19 | false 20 | true 21 | 102 22 | Job Type 23 | 24 | 25 | false 26 | true 27 | 103 28 | Salary Band 29 | 30 | 31 | false 32 | true 33 | 104 34 | Contract Type 35 | 36 | 37 | false 38 | true 39 | 105 40 | Hours 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/rest-api-service-description.json: -------------------------------------------------------------------------------- 1 | { 2 | "invokable": false, 3 | "uriTemplate": "restapi", 4 | "sub": { 5 | "jobinfo": { 6 | "uriTemplate": "jobinfo", 7 | "invokable": true, 8 | "params": { 9 | "jobid": { "type": "integer", "required": true } 10 | }, 11 | "sub": { 12 | "search": { 13 | "uriTemplate": "search", 14 | "invokable": true, 15 | "params": { }, 16 | "sub": { 17 | "full": { 18 | "invokable": true, 19 | "uriTemplate": "full" 20 | }, 21 | "facets": { 22 | "invokable": true, 23 | "uriTemplate": "facets" 24 | } 25 | } 26 | } 27 | } 28 | }, 29 | "employer": { 30 | "invokable": true, 31 | "params": { 32 | "id": { "type": "integer" } 33 | }, 34 | "uriTemplate": "employer", 35 | "sub": { 36 | "search": { 37 | "invokable": true, 38 | "uriTemplate": "search" 39 | } 40 | } 41 | }, 42 | "myjobs": { 43 | "uriTemplate": "myjobs", 44 | "invokable": true, 45 | "sub": { 46 | "add": { 47 | "invokable": true, 48 | "uriTemplate": "add", 49 | "params": { 50 | "jobid": { "type": "integer" } 51 | } 52 | }, 53 | "delete": { 54 | "invokable": true, 55 | "uriTemplate": "list", 56 | "params": { 57 | "jobid": { "type": "integer" } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /lib/soap-templates/UpdateBilledJob.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{recruiterBillingId}} 6 | {{employerBillingId}} 7 | {{id}} 8 | {{#if startDateTime}}{{startDateTime}}{{/if}} 9 | {{#if endDateTime}}{{endDateTime}}{{/if}} 10 | {{#if applicationMethod}}{{applicationMethod}}{{/if}} 11 | {{#if applicationEmail}}{{applicationEmail}}{{/if}} 12 | {{#if externalApplicationUrl}}{{externalApplicationUrl}}{{/if}} 13 | 14 | {{#each properties}} 15 | 16 | {{@key}} 17 | {{this}} 18 | 19 | {{/each}} 20 | 21 | 22 | {{#each categorization.terms}} 23 | 24 | {{categoryId}} 25 | {{#if termIds}} 26 | 27 | {{#each termIds}} 28 | {{this}} 29 | {{/each}} 30 | 31 | {{/if}} 32 | 33 | {{/each}} 34 | 35 | {{#defined isBackFill}}{{isBackFill}}{{else}}{{/defined}} 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/replies/soap/SoapFault.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | soap:Server 5 | System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> JobBoard.WebService.JobBoardException: An unexpected error occurred. ---> JobBoard.WebService.JobBoardException: The mandatory category:Location - ID:101 has no terms supplied 6 | at JobBoard.WebService.JobPostingImpl.ValidateCategories(WSSelectedTerms[] jobCategorization, ResolvedLocationIds resolvedLocationIds) 7 | at JobBoard.WebService.JobPostingImpl.AssignJobProperties(Guid UserID, Guid uiSecurityToken, ActionType actionType, Job job, String sJobID, String sStartDateTime, String sEndDateTime, String sApplicationMethod, String sApplicationEmail, String sExternalApplicationURL, WSJobPropertyValue[] jobProperties, WSSelectedTerms[] jobCategorization, DateTime maxAllowedEndDate, Int32 productDefinitionId, Nullable`1 isBackFill, Nullable`1 priceSoldAt) 8 | at JobBoard.WebService.JobPostingImpl.UpdateBilledJob(String sRecruiterBillingID, String sEmployerBillingID, String sJobID, String sEndDateTime, String sApplicationMethod, String sApplicationEmail, String sExternalApplicationURL, WSJobPropertyValue[] jobProperties, WSSelectedTerms[] jobCategorization, Nullable`1 isBackFill) 9 | at JobBoard.WebService.Billing.UpdateBilledJob(String sRecruiterBillingID, String sEmployerBillingID, String sJobID, String sEndDateTime, String sApplicationMethod, String sApplicationEmail, String sExternalApplicationURL, WSJobPropertyValue[] jobProperties, WSSelectedTerms[] jobCategorization, Nullable`1 isBackFill) 10 | --- End of inner exception stack trace --- 11 | at JobBoard.WebService.Billing.UpdateBilledJob(String sRecruiterBillingID, String sEmployerBillingID, String sJobID, String sEndDateTime, String sApplicationMethod, String sApplicationEmail, String sExternalApplicationURL, WSJobPropertyValue[] jobProperties, WSSelectedTerms[] jobCategorization, Nullable`1 isBackFill) 12 | --- End of inner exception stack trace --- 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/soap-templates/AddBilledJob.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{recruiterBillingId}} 6 | {{employerBillingId}} 7 | {{id}} 8 | {{#if startDateTime}}{{startDateTime}}{{/if}} 9 | {{#if endDateTime}}{{endDateTime}}{{/if}} 10 | {{#if applicationMethod}}{{applicationMethod}}{{/if}} 11 | {{#if applicationEmail}}{{applicationEmail}}{{/if}} 12 | {{#if externalApplicationUrl}}{{externalApplicationUrl}}{{/if}} 13 | 14 | {{#each properties}} 15 | 16 | {{@key}} 17 | {{this}} 18 | 19 | {{/each}} 20 | 21 | 22 | {{#each categorization.terms}} 23 | 24 | {{categoryId}} 25 | {{#if termIds}} 26 | 27 | {{#each termIds}} 28 | {{this}} 29 | {{/each}} 30 | 31 | {{/if}} 32 | 33 | {{/each}} 34 | 35 | {{#if upsells}} 36 | 37 | {{#each upsells}} 38 | 39 | {{this.id}} 40 | {{#if this.name}}{{this.name}}{{/if}} 41 | 42 | {{/each}} 43 | 44 | {{/if}} 45 | {{#defined productId}}{{productId}}{{else}}{{/defined}} 46 | {{#defined priceSoldAt}}{{priceSoldAt}}{{else}}{{/defined}} 47 | {{#defined isBackFill}}{{isBackFill}}{{else}}{{/defined}} 48 | 49 | 50 | -------------------------------------------------------------------------------- /test/replies/soap/GetCategoryTerms.many.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1 7 | Agriculture, Food and Veterinary 8 | 9 | 10 | 2 11 | Architecture, Building and Planning 12 | 13 | 14 | 3 15 | Biological Sciences 16 | 17 | 18 | 4 19 | Business and Management Studies 20 | 21 | 22 | 5 23 | Computer Science 24 | 25 | 26 | 6 27 | Creative Arts and Design 28 | 29 | 30 | 7 31 | Economics 32 | 33 | 34 | 8 35 | Education Studies 36 | 37 | 38 | 9 39 | Engineering and Technology 40 | 41 | 42 | 10 43 | Health and Medical 44 | 45 | 46 | 11 47 | Historical and Philosophical Studies 48 | 49 | 50 | 12 51 | Information Management and Librarianship 52 | 53 | 54 | 13 55 | Languages, Literature and Culture 56 | 57 | 58 | 14 59 | Law 60 | 61 | 62 | 15 63 | Mathematics and Statistics 64 | 65 | 66 | 16 67 | Media and Communications 68 | 69 | 70 | 17 71 | Physical and Environmental Sciences 72 | 73 | 74 | 18 75 | Politics and Government 76 | 77 | 78 | 19 79 | Psychology 80 | 81 | 82 | 20 83 | Social Sciences and Social Care 84 | 85 | 86 | 21 87 | Sport and Leisure 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/rest-api-client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var oauth = require('oauth'), 4 | debug = require('debug')('node-madgex'), 5 | extend = require('extend'), 6 | qs = require('querystring'), 7 | when = require('when'), 8 | getUrlFactory = function (baseUrl, requestOptions) { 9 | return function (operation, params, pathOnly) { 10 | params = extend({}, params, requestOptions || { deviceId: 'node-device' }); 11 | var result = operation + "?" + qs.stringify(params); 12 | if (!pathOnly) { 13 | result = baseUrl + result; 14 | } 15 | debug('url: %s', result); 16 | return result; 17 | }; 18 | }; 19 | 20 | function getDefaulOptions() { 21 | //deviceId is a mandatory request parameter for the Madgex REST api user can set this during the createClient call 22 | return { 23 | requestOptions: { deviceId: 'node-device' } 24 | }; 25 | } 26 | 27 | var serviceDefinition = require('./rest-api-service-description.json'); 28 | 29 | module.exports = function (baseUrl, credentials, options) { 30 | 31 | var request = new oauth.OAuth(null, null, credentials.key, credentials.secret, '1.0', null, 'HMAC-SHA1'), 32 | options = options || getDefaulOptions(), 33 | urlFactory = getUrlFactory(baseUrl, options.requestOptions); 34 | 35 | function buildFacade(sd, parentPath) { 36 | var result = buildFacadeItem(sd, parentPath); 37 | sd.sub && Object.keys(sd.sub).forEach(function (key) { 38 | result[key] = buildFacade(sd.sub[key], parentPath + "/" + sd.uriTemplate); 39 | }); 40 | return result; 41 | } 42 | 43 | function buildFacadeItem(sd, parentPath) { 44 | if (!sd.invokable) return {}; 45 | 46 | var path = (parentPath + "/" + sd.uriTemplate).replace("//", "/"); 47 | var result = function (params, done) { 48 | var getResult = function (handleResponse) { 49 | request.get(urlFactory(path, params), null, null, function (error, data) { 50 | if (error) return handleResponse(error); 51 | try { 52 | data = JSON.parse(data); 53 | } 54 | catch (err) { 55 | return handleResponse(err); 56 | } 57 | if (data.status !== "ok") { 58 | var message = JSON.stringify(data.result); 59 | if (Array.isArray(data.result.errors)) { 60 | var firstError = data.result.errors[0]; 61 | if (firstError.message) { 62 | message = firstError.message; 63 | } 64 | } 65 | return handleResponse(new Error(message)); 66 | } 67 | handleResponse(null, data.result); 68 | }); 69 | }; 70 | 71 | if (done && typeof done === 'function') { 72 | return getResult(done); 73 | } 74 | 75 | return when.promise(function (resolve, reject) { 76 | return getResult(function (err, data) { 77 | return (err) ? reject(err) : resolve(data); 78 | }); 79 | }); 80 | 81 | }; 82 | result.path = path; 83 | result.getPathAndQuery = function (params) { 84 | return urlFactory(result.path, params, true); 85 | }; 86 | return result; 87 | } 88 | 89 | var restApi = buildFacade(serviceDefinition, ""); 90 | restApi.urlFactory = urlFactory; 91 | return restApi; 92 | }; 93 | -------------------------------------------------------------------------------- /test/replies/rest/get.jobinfo.jobid.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "result": { 4 | "id": 1257, 5 | "title": "Senior Lecturer in Criminology", 6 | "posted": "21 Apr 2015", 7 | "description": "NEWMAN UNIVERSITY\r\n\r\n\r\n\r\nSenior Lecturer in Criminology\r\n\r\n\r\n\r\nSalary scale: \u00a336,308 to \u00a347,328 per annum\r\n\r\n\r\n\r\nRequired from 1st August 2015 (or as soon as possible thereafter), an experienced senior lecturer to contribute to the leadership and further development of our new BA (Hons) Criminology degree. With a relevant master's qualification and demonstrable expertise in criminal justice and policing, the successful applicant will teach on a broad range of undergraduate modules on Criminology and related applied social science programmes. Demonstrably research active in a relevant field of criminology, you will be an effective networker with the ability to establish appropriate external links to enhance the curriculum and the student experience.\r\n\r\n\r\n\r\nThis post is offered on a full-time, permanent basis, and candidates for appointment as senior lecturer must hold or have recently submitted for a doctoral qualification in a relevant area of criminology \/ criminal justice.\r\n\r\n\r\n\r\nApplicants wishing to have an informal discussion about the post should contact Frank Leishman, Dean of School - Human Sciences on 0121 476 1181 ext 2228.\r\n\r\n\r\n\r\nFurther details of posts and an application form may be obtained from:\r\n\r\n\r\n\r\nwww.newman.ac.uk\/jobs or alternatively e-mail: recruitment@newman.ac.uk or telephone 0121 476 1181 ext 2398.\r\n\r\n\r\n\r\nClosing date for applications: Friday 20th March 2015\r\n", 8 | "recruiter": "Newman University", 9 | "recruiterID": 1255, 10 | "recruiterLogo": "6583fa39-0812-4142-b3c6-d8662b488790", 11 | "salary": "\u00a336,308 to \u00a347,328 per annum", 12 | "location": "Birmingham, West Midlands", 13 | "geo": [ 14 | 15 | ], 16 | "jobRole": null, 17 | "applyInfo": { 18 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1257\/senior-lecturer-in-criminology\/?trackid=22", 19 | "type": "external-redirect", 20 | "allowMobileExternalRedirects": true, 21 | "externalApplicationUrl": "http:\/\/www.newman.ac.uk\/jobs" 22 | }, 23 | "upsells": [ 24 | 5, 25 | 6, 26 | 7, 27 | 8 28 | ], 29 | "noOfVideos": 0, 30 | "isPremiumJob": false, 31 | "isHomePageJob": false, 32 | "isDisplayLogoJob": true, 33 | "isTopJob": false, 34 | "categories": { 35 | "function": [ 36 | [ 37 | "Academic Posts", 38 | [ 39 | "Lecturers \/ Assistant Professors" 40 | ] 41 | ] 42 | ], 43 | "location": [ 44 | 45 | ], 46 | "discipline": [ 47 | "Psychology", 48 | "Social Sciences and Social Care" 49 | ], 50 | "contract": [ 51 | "Permanent" 52 | ], 53 | "salary": [ 54 | 55 | ] 56 | }, 57 | "urlId": "1257\/senior-lecturer-in-criminology", 58 | "usesAltRecruiterName": false, 59 | "ref": "", 60 | "status": "Live", 61 | "jobFunction": [ 62 | [ 63 | "Academic Posts", 64 | [ 65 | "Lecturers \/ Assistant Professors" 66 | ] 67 | ] 68 | ], 69 | "jobDiscipline": [ 70 | "Psychology", 71 | "Social Sciences and Social Care" 72 | ], 73 | "contractType": [ 74 | "Permanent" 75 | ], 76 | "contactInfo": { 77 | "contactTitle": null, 78 | "contactFirstName": null, 79 | "contactLastName": null, 80 | "contactLastName2": null, 81 | "contactAddress1": "", 82 | "contactAddress2": "", 83 | "contactAddress3": "", 84 | "contactTown": "", 85 | "contactCounty": "", 86 | "contactPostcode": "", 87 | "contactCountry": "", 88 | "contactPhone": "", 89 | "contactFax": "", 90 | "contactEmail": null 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /lib/soap-templates/AddRecruiterV2.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{#if recruiterName}}{{recruiterName}}{{/if}} 7 | {{#if customerBillingID}}{{customerBillingID}}{{/if}} 8 | {{#if contactFirstName}}{{contactFirstName}}{{/if}} 9 | {{#if contactLastName}}{{contactLastName}}{{/if}} 10 | {{#if contactLastName2}}{{contactLastName2}}{{/if}} 11 | {{#if address1}}{{address1}}{{/if}} 12 | {{#if address2}}{{address2}}{{/if}} 13 | {{#if address3}}{{address3}}{{/if}} 14 | {{#if townCity}}{{townCity}}{{/if}} 15 | {{#if countyState}}{{countyState}}{{/if}} 16 | {{#if zipPostCode}}{{zipPostCode}}{{/if}} 17 | {{#if country}}{{country}}{{/if}} 18 | {{#if telephone}}{{telephone}}{{/if}} 19 | {{#if contactEmail}}{{contactEmail}}{{/if}} 20 | {{recruiterTypeID}} 21 | {{#if image}} 22 | 23 | {{#if image.assetId}}{{image.assetId}}{{/if}} 24 | {{#if image.base64BlobString}}{{image.base64BlobString}}{{/if}} 25 | 26 | {{/if}} 27 | {{#if enhancedJobHtml}} 28 | 29 | {{#if enhancedJobHtml.jobTitleTextCss}}{{enhancedJobHtml.jobTitleTextCss}}{{/if}} 30 | {{#if enhancedJobHtml.jobHeadingsTextCss}}{{enhancedJobHtml.jobHeadingsTextCss}}{{/if}} 31 | {{#if enhancedJobHtml.buttonBackgroundCss}}{{enhancedJobHtml.buttonBackgroundCss}}{{/if}} 32 | {{#if enhancedJobHtml.buttonTextCss}}{{enhancedJobHtml.buttonTextCss}}{{/if}} 33 | {{#if enhancedJobHtml.hyperLinkCss}}{{enhancedJobHtml.hyperLinkCss}}{{/if}} 34 | {{#if enhancedJobHtml.banner}} 35 | 36 | {{#if enhancedJobHtml.banner.assetId}}{{enhancedJobHtml.banner.assetId}}{{/if}} 37 | {{#if enhancedJobHtml.banner.base64BlobString}}{{enhancedJobHtml.banner.base64BlobString}}{{/if}} 38 | 39 | {{/if}} 40 | 41 | {{/if}} 42 | {{#if accountManager}} 43 | 44 | {{#if accountManager.emailAddress}}{{accountManager.emailAddress}}{{/if}} 45 | {{#if accountManager.firstName}}{{accountManager.firstName}}{{/if}} 46 | {{#if accountManager.lastName}}{{accountManager.lastName}}{{/if}} 47 | 48 | {{/if}} 49 | {{#defined canAccessCvDatabase}}{{canAccessCvDatabase}}{{/defined}} 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/soap-templates/UpdateRecruiterWithBillingID.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{#if recruiterName}}{{recruiterName}}{{/if}} 7 | {{#if customerBillingID}}{{customerBillingID}}{{/if}} 8 | {{#if contactFirstName}}{{contactFirstName}}{{/if}} 9 | {{#if contactLastName}}{{contactLastName}}{{/if}} 10 | {{#if contactLastName2}}{{contactLastName2}}{{/if}} 11 | {{#if address1}}{{address1}}{{/if}} 12 | {{#if address2}}{{address2}}{{/if}} 13 | {{#if address3}}{{address3}}{{/if}} 14 | {{#if townCity}}{{townCity}}{{/if}} 15 | {{#if countyState}}{{countyState}}{{/if}} 16 | {{#if zipPostCode}}{{zipPostCode}}{{/if}} 17 | {{#if country}}{{country}}{{/if}} 18 | {{#if telephone}}{{telephone}}{{/if}} 19 | {{#if contactEmail}}{{contactEmail}}{{/if}} 20 | {{recruiterTypeID}} 21 | {{#if image}} 22 | 23 | {{#if image.assetId}}{{image.assetId}}{{/if}} 24 | {{#if image.base64BlobString}}{{image.base64BlobString}}{{/if}} 25 | 26 | {{/if}} 27 | {{#if enhancedJobHtml}} 28 | 29 | {{#if enhancedJobHtml.jobTitleTextCss}}{{enhancedJobHtml.jobTitleTextCss}}{{/if}} 30 | {{#if enhancedJobHtml.jobHeadingsTextCss}}{{enhancedJobHtml.jobHeadingsTextCss}}{{/if}} 31 | {{#if enhancedJobHtml.buttonBackgroundCss}}{{enhancedJobHtml.buttonBackgroundCss}}{{/if}} 32 | {{#if enhancedJobHtml.buttonTextCss}}{{enhancedJobHtml.buttonTextCss}}{{/if}} 33 | {{#if enhancedJobHtml.hyperLinkCss}}{{enhancedJobHtml.hyperLinkCss}}{{/if}} 34 | {{#if enhancedJobHtml.banner}} 35 | 36 | {{#if enhancedJobHtml.banner.assetId}}{{enhancedJobHtml.banner.assetId}}{{/if}} 37 | {{#if enhancedJobHtml.banner.base64BlobString}}{{enhancedJobHtml.banner.base64BlobString}}{{/if}} 38 | 39 | {{/if}} 40 | 41 | {{/if}} 42 | {{#if accountManager}} 43 | 44 | {{#if accountManager.emailAddress}}{{accountManager.emailAddress}}{{/if}} 45 | {{#if accountManager.firstName}}{{accountManager.firstName}}{{/if}} 46 | {{#if accountManager.lastName}}{{accountManager.lastName}}{{/if}} 47 | 48 | {{/if}} 49 | {{#defined canAccessCvDatabase}}{{canAccessCvDatabase}}{{/defined}} 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/soap-api-client.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var handlebars = require('handlebars') 5 | var format = require('util').format 6 | var path = require('path') 7 | var request = require('request') 8 | var xml2js = require('xml2js') 9 | var jsonPointer = require('json-pointer') 10 | var _ = require('lodash') 11 | var changeCase = require('change-case') 12 | var parseBooleans = require('./parseBooleans') 13 | var serviceDescription = require('./soap-api-service-description.json') 14 | var when = require('when') 15 | 16 | handlebars.registerHelper('defined', function(value, options) { 17 | return value !== undefined ? options.fn(this) : options.inverse(this) 18 | }); 19 | 20 | module.exports = function(baseUrl) { 21 | 22 | var api = {} 23 | 24 | _.each(serviceDescription, function(apiConfig, apiName) { 25 | 26 | api[apiName] = {} 27 | 28 | _.each(apiConfig.services, function(serviceConfig, serviceName) { 29 | var source = fs.readFileSync(path.join(__dirname, 'soap-templates', serviceName + '.hbs'), { 30 | encoding: 'UTF-8' 31 | }) 32 | var template = handlebars.compile(source) 33 | var url = baseUrl + apiConfig.path 34 | 35 | api[apiName][changeCase.camel(serviceName)] = function() { 36 | var args = Array.prototype.slice.call(arguments) 37 | var next = args.pop() 38 | var data = typeof next !== 'function' ? next : args.pop() 39 | var getResponse = function getResponse(handleResponse) { 40 | request({ 41 | url: url, 42 | method: 'POST', 43 | headers: { 44 | 'Content-Type': 'text/xml' 45 | }, 46 | body: template(data) 47 | }, function(err, response, body) { 48 | if (err) return handleResponse(new Error(format('POST %s failed. Original error was: %s', url, err.message))) 49 | if (!/^2/.test(response.statusCode)) return handleResponse(toError(url, response, body)) 50 | new xml2js.Parser({ 51 | trim: true, 52 | explicitArray: false, 53 | tagNameProcessors: [xml2js.processors.firstCharLowerCase, lowerCase('id')], 54 | attrNameProcessors: [xml2js.processors.firstCharLowerCase], 55 | valueProcessors: [xml2js.processors.parseNumbers, parseBooleans] 56 | }).parseString(body, function(err, obj) { 57 | if (err) return handleResponse(err) 58 | if (serviceConfig.forceArray) { 59 | var item = jsonPointer.get(obj, serviceConfig.result) 60 | if (!_.isArray(item)) jsonPointer.set(obj, serviceConfig.result, [item]) 61 | } 62 | handleResponse(null, jsonPointer.get(obj, serviceConfig.result)) 63 | }) 64 | }) 65 | } 66 | 67 | if (next && typeof next === 'function') { 68 | return getResponse(next) 69 | } 70 | 71 | return when.promise(function(resolve, reject) { 72 | return getResponse(function(err, results) { 73 | return (err) ? reject(err) : resolve(results); 74 | }) 75 | }); 76 | } 77 | }) 78 | }) 79 | 80 | function toError(url, response, body) { 81 | var err = new Error(format('POST %s failed. Status code was: %d', url, response.statusCode)) 82 | err.statusCode = response.statusCode 83 | new xml2js.Parser({ 84 | trim: true, 85 | explicitArray: false 86 | }).parseString(body, function(parseErr, data) { 87 | if (parseErr) return 88 | if (!jsonPointer.has(data, '/soap:Envelope/soap:Body/soap:Fault')) return 89 | err.faultCode = jsonPointer.get(data, '/soap:Envelope/soap:Body/soap:Fault/faultcode') 90 | err.faultString = jsonPointer.get(data, '/soap:Envelope/soap:Body/soap:Fault/faultstring') 91 | }) 92 | return err 93 | } 94 | 95 | 96 | function lowerCase() { 97 | var args = Array.prototype.slice.call(arguments) 98 | return function(str) { 99 | return args.indexOf(str.toLowerCase()) >= 0 ? str.toLowerCase() : str 100 | } 101 | } 102 | 103 | return api 104 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-madgex 2 | [![Build Status](https://travis-ci.org/guidesmiths/node-madgex.svg?branch=master)](https://travis-ci.org/guidesmiths/node-madgex) 3 | 4 | A node.js client for [Madgex](http://madgex.com) web services. 5 | 6 | ## About 7 | Madgex's web services are split between RESTful and SOAP APIs. This module currently supports only a subset of the APIs, but we would be delighted to receive pull requests for the methods that are missing. 8 | 9 | The current set of supported web services is 10 | 11 | ### REST API 12 | 13 | 1. getinfo 14 | 1. employer 15 | 1. myjobs 16 | 17 | ### Billing API 18 | 19 | 1. AddBilledJob 20 | 1. AddRecruiterV2 21 | 1. GetCategories 22 | 1. GetCategoryTerms 23 | 1. GetLocations 24 | 1. UpdateBilledJob 25 | 1. UpdateRecruiterWithBillingID 26 | 27 | 28 | ## The REST API 29 | 30 | ### Usage 31 | 32 | ```javascript 33 | var madgex = require('node-madgex') 34 | var client = madgex.createClient('http://yoursite-webservice.madgexjbtest.com', { key: 'yourkey', secret: 'yoursecret' }) 35 | 36 | client.restApi.jobinfo({ jobid: 1257 }, function(err, data) { 37 | console.log(data); 38 | }) 39 | ``` 40 | 41 | API methods usually accept a params hash and a completion callback with (err, data, result) signature; 42 | 43 | ### Promises 44 | As an alternative to the completion callback you can use promises as well. Api methods return with a promise 45 | that resolves after the completion callback (if one is present). 46 | 47 | ```javascript 48 | client.jobinfo({ jobid: 1257 }) 49 | .then(function(data) { 50 | //handle data 51 | }) 52 | .fail(function(err) { 53 | //dome something with the error 54 | }); 55 | ``` 56 | 57 | #### Chain'em 58 | 59 | Promised values are easy to compose: 60 | ```javascript 61 | client.jobinfo 62 | .search({}) 63 | .then(function(jobs) { return client.jobinfo({jobid: jobs[0].id }) }) 64 | .then(function(jobdetails) { /*handle data*/ }) 65 | .fail(function(err) { /*dome something with the error */ }); 66 | ``` 67 | 68 | ### Service Description 69 | The RESTful client API is dynamically built by code from the service description config file. 70 | Extend this to add new functions to the API. (/lib/rest-api-service-description.json) 71 | 72 | ### Service Methods 73 | 74 | #### jobinfo(params, done) 75 | Displays information about a job 76 | 77 | ##### params 78 | a hash with the following fields 79 | 80 | field | type,info 81 | --- | --- 82 | jobid | integer, required 83 | 84 | #### jobinfo.search(params, done) 85 | Searches in the job database 86 | 87 | ##### params 88 | field | type,info 89 | --- | --- 90 | keywords | free text with boolean expressions allowed, optional 91 | dateFrom | ISO format date 92 | dateTo | ISO format date 93 | 94 | ...and much more. refer to the Madgex REST documentation for full set of params. 95 | 96 | 97 | #### jobinfo.search.full(params, done) 98 | Same as search but returns full dataset 99 | 100 | #### jobinfo.search.facets(params, done) 101 | Return search refiners for a search result. Params are same as in search() 102 | 103 | #### employer(params, done) 104 | Displays information about am employer 105 | 106 | ##### params 107 | a hash with the following fields 108 | 109 | field | type,info 110 | --- | --- 111 | id | integer, required 112 | 113 | #### employer.search(params, done) 114 | Searches in the employer database 115 | 116 | #### myjobs.add(params, done) 117 | 118 | #### myjobs.delete(params, done) 119 | 120 | ## The SOAP API 121 | 122 | ### Usage 123 | ```javascript 124 | var madgex = require('node-madgex') 125 | var client = madgex.createClient('http://yoursite-webservice.madgexjbtest.com', { key: 'yourkey', secret: 'yoursecret' }) 126 | 127 | client.soapApi.billingApi.getCategoryTerms({ categoryId: 105 }, function(err, data) { 128 | console.log(data); 129 | }) 130 | ``` 131 | Each billingApi method takes an optional parameters object and typical callback. You can determine the available parameters names by inspecting the equivalent methods handlebars template (see ./lib/soap-templates/*.hbs). These are camel cased equivalents to the elements specified in the Madgex Billing API documentation. Working out which parameters are required and what their values should be requires a degree of experience. 132 | 133 | Responses stripped of their SOAPiness and converted to camelCased json. Integers, floats and booleans are parsed, so instead of 134 | 135 | ```xml 136 | 137 | 138 | 139 | 140 | 141 | false 142 | true 143 | 105 144 | Hours 145 | 146 | 147 | 148 | 149 | 150 | ``` 151 | 152 | you'll receive 153 | ```json 154 | [ 155 | { 156 | "mandatory": false, 157 | "multiSelect": true, 158 | "id": 105, 159 | "name": "hours" 160 | } 161 | ] 162 | ``` 163 | 164 | #### Error handling 165 | In the event of an HTTP error, the err object passed to your callback will be blessed with a 'statusCode' property. In the event ofa SOAP Fault, the err object will additionally be blessed with 'faultCode' and 'faultString' properties. 166 | 167 | #### Adding more SOAP API methods 168 | Adding more API methods is easy 169 | 170 | 1. Fork and clone node-madgex 171 | 2. Generate a real request and response using your tool of choice ([SoapUI](http://www.soapui.org/), curl, etc) 172 | 3. Convert the request to a handlbars template and save it in lib/soap-templates/MethodName.hbs, 173 | 4. Save the response in test/replies/soap/MethodName.ok.xml 174 | 5. Update lib/soap-api-service-description.json 175 | 6. Add one or more test cases 176 | 7. Submit a PR 177 | 178 | -------------------------------------------------------------------------------- /test/replies/soap/GetLocations.many.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 325 7 | 8 | London (Greater) 9 | GB 10 | 5 11 | 12 | 13 | 1500 14 | 15 | London (Central) 16 | GB 17 | 6 18 | 19 | 20 | 289 21 | 22 | Londonderry 23 | GB 24 | 5 25 | 26 | 27 | 5920616 28 | 29 | London (East) 30 | GB 31 | 6 32 | 33 | 34 | 5920618 35 | 36 | London (West) 37 | GB 38 | 6 39 | 40 | 41 | 5920615 42 | 43 | London (North) 44 | GB 45 | 6 46 | 47 | 48 | 5920617 49 | 50 | London (South) 51 | GB 52 | 6 53 | 54 | 55 | 538 56 | 57 | Longhope 58 | GB 59 | 8 60 | 61 | 62 | 1819 63 | 64 | Longfield 65 | GB 66 | 8 67 | 68 | 69 | 1268 70 | 71 | Longniddry 72 | GB 73 | 8 74 | 75 | 76 | 660 77 | 78 | Londonderry 79 | GB 80 | 8 81 | 82 | 83 | 1784215 84 | 85 | London (City of) 86 | GB 87 | 9 88 | 89 | 90 | 13542 91 | 92 | Lonlas 93 | GB 94 | 10 95 | 96 | 97 | 5172 98 | 99 | Lonmay 100 | GB 101 | 10 102 | 103 | 104 | 26439 105 | 106 | Longcot 107 | GB 108 | 10 109 | 110 | 111 | 10374 112 | 113 | Longden 114 | GB 115 | 10 116 | 117 | 118 | 3122 119 | 120 | Longdon 121 | GB 122 | 10 123 | 124 | 125 | 16462 126 | 127 | Longdon 128 | GB 129 | 10 130 | 131 | 132 | 16313 133 | 134 | Longham 135 | GB 136 | 10 137 | 138 | 139 | 17116 140 | 141 | Longney 142 | GB 143 | 10 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /test/rest-api-tests.js: -------------------------------------------------------------------------------- 1 | var madgex = require('../index.js'), 2 | assert = require('assert'), 3 | fs = require('fs'), 4 | path = require('path'), 5 | nock = require('nock') 6 | 7 | describe('Madgex Client REST API', function () { 8 | 9 | this.timeout(5000); 10 | 11 | var client = madgex.createClient('http://guidesmiths-webservice.madgexjbtest.com', { 12 | key: "madgex-test-key", 13 | secret: "madgex-test-secret" 14 | }).restApi; 15 | 16 | it("should have a jobInfo API part", function () { 17 | assert.ok(client.jobinfo, "jobInfo api branch not found"); 18 | }); 19 | 20 | it("should provide a path info from method invocation", function () { 21 | 22 | assert.ok(client.jobinfo.getPathAndQuery, "method has not path discover method"); 23 | assert.equal(client.jobinfo.getPathAndQuery({}), "/restapi/jobinfo?deviceId=node-device", 24 | "method path resolution is incorrect"); 25 | }); 26 | 27 | describe("jobinfo API function", function () { 28 | it("shoud be invokable", function () { 29 | assert.equal(typeof client.jobinfo, "function", "jobinfo is not a function"); 30 | }) 31 | it("should return info on a job", function (done) { 32 | 33 | nock('http://guidesmiths-webservice.madgexjbtest.com:80') 34 | .get('/restapi/jobinfo?jobid=1257&deviceId=node-device') 35 | .replyWithFile(200, __dirname + '/replies/rest/get.jobinfo.jobid.json') 36 | 37 | client.jobinfo({ jobid: 1257 }, function (err, data) { 38 | assert.equal(null, err, "error is not null"); 39 | assert.equal(data.id, 1257, "jobid id does not match"); 40 | done(); 41 | }); 42 | }) 43 | 44 | it("should have a search function", function () { 45 | assert.ok(client.jobinfo, "jobInfo api branch not found"); 46 | }); 47 | describe("jobinfo.search API function ", function () { 48 | it("should return 30 items without params", function (done) { 49 | 50 | nock('http://guidesmiths-webservice.madgexjbtest.com:80') 51 | .get('/restapi/jobinfo/search?deviceId=node-device') 52 | .replyWithFile(200, __dirname + '/replies/rest/get.jobinfo.search.json') 53 | 54 | client.jobinfo.search({}, function(err, data) { 55 | assert.ok(data.jobs, "search yielded not jobs"); 56 | assert.equal(data.jobs.length, 30, "search resulted incorred number of items"); 57 | done(); 58 | }); 59 | }); 60 | it("should also return a promise the resolves to 30 jobs", function (done) { 61 | 62 | nock('http://guidesmiths-webservice.madgexjbtest.com:80') 63 | .get('/restapi/jobinfo/search?deviceId=node-device') 64 | .replyWithFile(200, __dirname + '/replies/rest/get.jobinfo.search.json') 65 | 66 | var jobs = client.jobinfo.search({}); 67 | assert.ok(jobs.then, "result is not a promise"); 68 | jobs.then(function (data) { 69 | assert.equal(data.jobs.length, 30, "item count mismatch"); 70 | done(); 71 | }); 72 | }) 73 | it("should have a full subfunction", function () { 74 | assert.ok(client) 75 | }); 76 | it("should have a facets subfunction", function () { 77 | assert.ok(client) 78 | }); 79 | 80 | describe("jobinfo.search.full API function", function () { 81 | it("should return 30 items without params", function (done) { 82 | 83 | nock('http://guidesmiths-webservice.madgexjbtest.com:80') 84 | .get('/restapi/jobinfo/search/full?deviceId=node-device') 85 | .replyWithFile(200, __dirname + '/replies/rest/get.jobinfo.search.full.json') 86 | 87 | client.jobinfo.search.full({}, function (err, data) { 88 | assert.equal(data.jobs.length, 30, "jobs item count mismatch") 89 | done(); 90 | }); 91 | }); 92 | 93 | }); 94 | 95 | describe("jobinfo.search.facets API function", function () { 96 | it("should return jobType facet with 20 items without params", function (done) { 97 | 98 | nock('http://guidesmiths-webservice.madgexjbtest.com:80') 99 | .get('/restapi/jobinfo/search/facets?deviceId=node-device') 100 | .replyWithFile(200, __dirname + '/replies/rest/get.jobinfo.search.facets.json') 101 | 102 | 103 | client.jobinfo.search.facets({}, function (err, data) { 104 | assert.ok(data.jobType.length >= 20, "jobs item count mismatch") 105 | //done(); 106 | //console.log(data.jobType.length); 107 | done(); 108 | }); 109 | }); 110 | }); 111 | }); 112 | }); 113 | 114 | it("should have an employer API part", function () { 115 | assert.ok(client.employer, "employer api branch not found"); 116 | }); 117 | 118 | describe("employer API function", function () { 119 | it("should be invokable", function () { 120 | 121 | }); 122 | it("should have a search function", function () { 123 | assert.equal(typeof client.employer,"function", "employer must be a function"); 124 | }); 125 | 126 | describe("employer.search", function () { 127 | it("should find employers without params", function (done) { 128 | 129 | nock('http://guidesmiths-webservice.madgexjbtest.com:80') 130 | .get('/restapi/employer/search?deviceId=node-device') 131 | .replyWithFile(200, __dirname + '/replies/rest/get.employer.search.json') 132 | 133 | client.employer.search({}, function (err, data) { 134 | assert.equal(data.employers.length, 30, "employers count mismatch"); 135 | done(); 136 | }); 137 | }); 138 | 139 | }) 140 | }); 141 | 142 | 143 | it("should have a myjobs API part", function () { 144 | assert.ok(client.myjobs, "api part is missing"); 145 | }); 146 | 147 | describe("myjobs API part", function () { 148 | it("should provide the 'add' method", function () { 149 | assert.ok(client.myjobs.add, "add function not found"); 150 | }); 151 | it("should provide the 'delete' method", function () { 152 | assert.ok(client.myjobs.delete, "add function not found"); 153 | }); 154 | }); 155 | }) 156 | 157 | 158 | -------------------------------------------------------------------------------- /test/replies/rest/get.jobinfo.search.facets.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "result": { 4 | "totalResults": 132, 5 | "recruiterType": [ 6 | { 7 | "id": -20, 8 | "name": "Recruitment Consultant", 9 | "count": 1 10 | }, 11 | { 12 | "id": -10, 13 | "name": "Direct Employer", 14 | "count": 132 15 | } 16 | ], 17 | "academicDiscipline": [ 18 | { 19 | "id": 1, 20 | "name": "Agriculture, Food and Veterinary", 21 | "count": 6 22 | }, 23 | { 24 | "id": 2, 25 | "name": "Architecture, Building and Planning", 26 | "count": 5 27 | }, 28 | { 29 | "id": 3, 30 | "name": "Biological Sciences", 31 | "count": 8 32 | }, 33 | { 34 | "id": 4, 35 | "name": "Business and Management Studies", 36 | "count": 16 37 | }, 38 | { 39 | "id": 5, 40 | "name": "Computer Science", 41 | "count": 11 42 | }, 43 | { 44 | "id": 6, 45 | "name": "Creative Arts and Design", 46 | "count": 12 47 | }, 48 | { 49 | "id": 7, 50 | "name": "Economics", 51 | "count": 8 52 | }, 53 | { 54 | "id": 8, 55 | "name": "Education Studies", 56 | "count": 8 57 | }, 58 | { 59 | "id": 9, 60 | "name": "Engineering and Technology", 61 | "count": 13 62 | }, 63 | { 64 | "id": 10, 65 | "name": "Health and Medical", 66 | "count": 8 67 | }, 68 | { 69 | "id": 11, 70 | "name": "Historical and Philosophical Studies", 71 | "count": 5 72 | }, 73 | { 74 | "id": 12, 75 | "name": "Information Management and Librarianship", 76 | "count": 7 77 | }, 78 | { 79 | "id": 13, 80 | "name": "Languages, Literature and Culture", 81 | "count": 13 82 | }, 83 | { 84 | "id": 14, 85 | "name": "Law", 86 | "count": 8 87 | }, 88 | { 89 | "id": 15, 90 | "name": "Mathematics and Statistics", 91 | "count": 8 92 | }, 93 | { 94 | "id": 16, 95 | "name": "Media and Communications", 96 | "count": 12 97 | }, 98 | { 99 | "id": 17, 100 | "name": "Physical and Environmental Sciences", 101 | "count": 4 102 | }, 103 | { 104 | "id": 18, 105 | "name": "Politics and Government", 106 | "count": 9 107 | }, 108 | { 109 | "id": 19, 110 | "name": "Psychology", 111 | "count": 10 112 | }, 113 | { 114 | "id": 20, 115 | "name": "Social Sciences and Social Care", 116 | "count": 8 117 | }, 118 | { 119 | "id": 21, 120 | "name": "Sport and Leisure", 121 | "count": 6 122 | } 123 | ], 124 | "jobType": [ 125 | { 126 | "id": 22, 127 | "name": "Senior Management & Heads of Department", 128 | "count": 51 129 | }, 130 | { 131 | "id": 23, 132 | "name": "Vice-chancellors \/ Principals \/ Provosts", 133 | "count": 3 134 | }, 135 | { 136 | "id": 24, 137 | "name": "Deputy Vice-chancellors", 138 | "count": 7 139 | }, 140 | { 141 | "id": 25, 142 | "name": "Pro Vice-chancellors", 143 | "count": 4 144 | }, 145 | { 146 | "id": 26, 147 | "name": "Deans", 148 | "count": 6 149 | }, 150 | { 151 | "id": 27, 152 | "name": "Heads of Department", 153 | "count": 11 154 | }, 155 | { 156 | "id": 28, 157 | "name": "Chief Operating Officers", 158 | "count": 4 159 | }, 160 | { 161 | "id": 29, 162 | "name": "Registrars \/ Assistant Registrars", 163 | "count": 3 164 | }, 165 | { 166 | "id": 30, 167 | "name": "Directors", 168 | "count": 13 169 | }, 170 | { 171 | "id": 31, 172 | "name": "Other Senior Management", 173 | "count": 9 174 | }, 175 | { 176 | "id": 32, 177 | "name": "Academic Posts", 178 | "count": 67 179 | }, 180 | { 181 | "id": 33, 182 | "name": "Professors \/ Chairs", 183 | "count": 8 184 | }, 185 | { 186 | "id": 34, 187 | "name": "Readers \/ Senior Research Fellows", 188 | "count": 5 189 | }, 190 | { 191 | "id": 35, 192 | "name": "Principal \/ Senior Lecturers \/ Associate Professors", 193 | "count": 25 194 | }, 195 | { 196 | "id": 36, 197 | "name": "Lecturers \/ Assistant Professors", 198 | "count": 28 199 | }, 200 | { 201 | "id": 37, 202 | "name": "Tutors", 203 | "count": 1 204 | }, 205 | { 206 | "id": 38, 207 | "name": "Fellowships", 208 | "count": 4 209 | }, 210 | { 211 | "id": 39, 212 | "name": "Postdocs", 213 | "count": 5 214 | }, 215 | { 216 | "id": 40, 217 | "name": "Studentships", 218 | "count": 9 219 | }, 220 | { 221 | "id": 41, 222 | "name": "Other Academic", 223 | "count": 2 224 | }, 225 | { 226 | "id": 42, 227 | "name": "Academic Related", 228 | "count": 16 229 | }, 230 | { 231 | "id": 43, 232 | "name": "Librarians", 233 | "count": 2 234 | }, 235 | { 236 | "id": 44, 237 | "name": "Research Assistants", 238 | "count": 4 239 | }, 240 | { 241 | "id": 45, 242 | "name": "Technicians", 243 | "count": 5 244 | }, 245 | { 246 | "id": 46, 247 | "name": "Other Academic Related", 248 | "count": 2 249 | }, 250 | { 251 | "id": 47, 252 | "name": "Professional Services", 253 | "count": 30 254 | }, 255 | { 256 | "id": 48, 257 | "name": "Administrative", 258 | "count": 3 259 | }, 260 | { 261 | "id": 49, 262 | "name": "Estates and Facilities", 263 | "count": 2 264 | }, 265 | { 266 | "id": 50, 267 | "name": "Finance and Procurement", 268 | "count": 6 269 | }, 270 | { 271 | "id": 51, 272 | "name": "Fundraising, Development and Alumni", 273 | "count": 8 274 | }, 275 | { 276 | "id": 52, 277 | "name": "Human Resources", 278 | "count": 4 279 | }, 280 | { 281 | "id": 53, 282 | "name": "International Activities", 283 | "count": 2 284 | }, 285 | { 286 | "id": 54, 287 | "name": "IT Services", 288 | "count": 1 289 | }, 290 | { 291 | "id": 55, 292 | "name": "Knowledge Transfer \/ Business Links", 293 | "count": 4 294 | }, 295 | { 296 | "id": 56, 297 | "name": "Library \/ Records \/ Information Management", 298 | "count": 2 299 | }, 300 | { 301 | "id": 57, 302 | "name": "Marketing and Communications", 303 | "count": 3 304 | }, 305 | { 306 | "id": 58, 307 | "name": "Planning ", 308 | "count": 3 309 | }, 310 | { 311 | "id": 59, 312 | "name": "Registry", 313 | "count": 3 314 | }, 315 | { 316 | "id": 60, 317 | "name": "Student Recruitment \/ Admissions", 318 | "count": 2 319 | }, 320 | { 321 | "id": 61, 322 | "name": "Student Services", 323 | "count": 1 324 | }, 325 | { 326 | "id": 62, 327 | "name": "Other Professional Services", 328 | "count": 2 329 | } 330 | ], 331 | "salaryBand": [ 332 | { 333 | "id": 63, 334 | "name": "\u00a30-\u00a310,000", 335 | "count": 5 336 | }, 337 | { 338 | "id": 64, 339 | "name": "\u00a310,001 - \u00a320,000", 340 | "count": 2 341 | }, 342 | { 343 | "id": 65, 344 | "name": "\u00a320,001 - \u00a330,000", 345 | "count": 3 346 | }, 347 | { 348 | "id": 66, 349 | "name": "\u00a330,001 - \u00a340,000", 350 | "count": 6 351 | }, 352 | { 353 | "id": 67, 354 | "name": "\u00a340,001 - \u00a350,000", 355 | "count": 1 356 | }, 357 | { 358 | "id": 68, 359 | "name": "\u00a350,001 - \u00a360,000", 360 | "count": 2 361 | }, 362 | { 363 | "id": 70, 364 | "name": "\u00a370,001 - \u00a380,000", 365 | "count": 2 366 | }, 367 | { 368 | "id": 71, 369 | "name": "\u00a380,001 - \u00a390,000", 370 | "count": 1 371 | }, 372 | { 373 | "id": 72, 374 | "name": "\u00a390,001 - \u00a3100,000", 375 | "count": 3 376 | }, 377 | { 378 | "id": 73, 379 | "name": "\u00a3100,001 - \u00a3110,000", 380 | "count": 4 381 | }, 382 | { 383 | "id": 74, 384 | "name": "\u00a3110,001 - \u00a3120,000", 385 | "count": 4 386 | }, 387 | { 388 | "id": 75, 389 | "name": "\u00a3120,001 - \u00a3130,000", 390 | "count": 1 391 | }, 392 | { 393 | "id": 76, 394 | "name": "\u00a3130,001 - \u00a3140,000", 395 | "count": 6 396 | }, 397 | { 398 | "id": 77, 399 | "name": "\u00a3140,001 - \u00a3150,000", 400 | "count": 2 401 | }, 402 | { 403 | "id": 78, 404 | "name": "\u00a3150,001 - \u00a3160,000", 405 | "count": 2 406 | }, 407 | { 408 | "id": 80, 409 | "name": "\u00a3170,001 - \u00a3180,000", 410 | "count": 1 411 | }, 412 | { 413 | "id": 81, 414 | "name": "\u00a3180,001 - \u00a3190,000", 415 | "count": 3 416 | }, 417 | { 418 | "id": 82, 419 | "name": "\u00a3190,001 - \u00a3200,000", 420 | "count": 3 421 | }, 422 | { 423 | "id": 84, 424 | "name": "Daily Rate", 425 | "count": 5 426 | }, 427 | { 428 | "id": 85, 429 | "name": "Hourly Rate", 430 | "count": 3 431 | } 432 | ], 433 | "contractType": [ 434 | { 435 | "id": 86, 436 | "name": "Permanent", 437 | "count": 105 438 | }, 439 | { 440 | "id": 87, 441 | "name": "Temporary", 442 | "count": 23 443 | }, 444 | { 445 | "id": 88, 446 | "name": "Fixed Term", 447 | "count": 25 448 | }, 449 | { 450 | "id": 89, 451 | "name": "Maternity Cover", 452 | "count": 19 453 | } 454 | ], 455 | "hours": [ 456 | { 457 | "id": 90, 458 | "name": "Full Time", 459 | "count": 110 460 | }, 461 | { 462 | "id": 91, 463 | "name": "Part Time", 464 | "count": 28 465 | }, 466 | { 467 | "id": 92, 468 | "name": "Sessional", 469 | "count": 28 470 | } 471 | ], 472 | "location": [ 473 | 474 | ] 475 | } 476 | } -------------------------------------------------------------------------------- /test/replies/rest/get.employer.search.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "result": { 4 | "totalResults": 45, 5 | "startIndex": 0, 6 | "itemsPerPage": 30, 7 | "employers": [ 8 | { 9 | "id": 1171, 10 | "title": "Anglia Ruskin University", 11 | "noOfLiveJobs": 1, 12 | "shortDescription": "", 13 | "recruiterLogoId": "53fb405d-c4af-4b3f-83f8-94d32fbdedfe", 14 | "employerTypes": [ 15 | "Direct Employer" 16 | ], 17 | "noOfVideos": 0 18 | }, 19 | { 20 | "id": 7, 21 | "title": "Apple (Madgex Test Data)", 22 | "noOfLiveJobs": 1, 23 | "shortDescription": "Are you about to graduate from college or graduate school? Did you graduate in the past year? Apple is hiring new grads.", 24 | "recruiterLogoId": "3999c39f-5794-4a05-a969-19b1abe032f8", 25 | "employerTypes": [ 26 | "Direct Employer" 27 | ], 28 | "noOfVideos": 0 29 | }, 30 | { 31 | "id": 8, 32 | "title": "Atari Group (Madgex Test Data)", 33 | "noOfLiveJobs": 2, 34 | "shortDescription": "Atari is one of the world's truly iconic gaming brands. Today we are transforming Atari into a 21st century digital entertainment company.", 35 | "recruiterLogoId": "0d153a46-d60c-42db-8f72-153bd2365fb6", 36 | "employerTypes": [ 37 | "Direct Employer" 38 | ], 39 | "noOfVideos": 0 40 | }, 41 | { 42 | "id": 1202, 43 | "title": "Bath Spa University", 44 | "noOfLiveJobs": 3, 45 | "shortDescription": "", 46 | "recruiterLogoId": "5fe21803-d4ce-4685-834d-b55555071cbb", 47 | "employerTypes": [ 48 | "Direct Employer" 49 | ], 50 | "noOfVideos": 0 51 | }, 52 | { 53 | "id": 9, 54 | "title": "BBC (Madgex Test Data)", 55 | "noOfLiveJobs": 1, 56 | "shortDescription": "It\u2019s our goal to become the most creative organisation in the world. In order to make it a reality, we need to keep on attracting the brightest, most creative and talented people.", 57 | "recruiterLogoId": "58ac51bc-a31d-48bc-b4ba-b53766d9f7f8", 58 | "employerTypes": [ 59 | "Direct Employer" 60 | ], 61 | "noOfVideos": 0 62 | }, 63 | { 64 | "id": 1295, 65 | "title": "Bournemouth University", 66 | "noOfLiveJobs": 1, 67 | "shortDescription": "", 68 | "recruiterLogoId": "9a019be4-0723-4757-bb83-fe61aa64f082", 69 | "employerTypes": [ 70 | "Direct Employer" 71 | ], 72 | "noOfVideos": 0 73 | }, 74 | { 75 | "id": 1180, 76 | "title": "Edge Hill University", 77 | "noOfLiveJobs": 4, 78 | "shortDescription": "", 79 | "recruiterLogoId": "f2c7218f-d2e7-4e1f-b9c4-cfb6cca57b39", 80 | "employerTypes": [ 81 | "Direct Employer" 82 | ], 83 | "noOfVideos": 0 84 | }, 85 | { 86 | "id": 1251, 87 | "title": "Edinburgh Business School", 88 | "noOfLiveJobs": 3, 89 | "shortDescription": "", 90 | "recruiterLogoId": "0fa5974c-0b4b-496e-8672-43f394608f5c", 91 | "employerTypes": [ 92 | "Direct Employer" 93 | ], 94 | "noOfVideos": 0 95 | }, 96 | { 97 | "id": 1249, 98 | "title": "Edinburgh Napier University", 99 | "noOfLiveJobs": 2, 100 | "shortDescription": "", 101 | "recruiterLogoId": "65d93e49-51fe-466e-82e2-6bb0336eda25", 102 | "employerTypes": [ 103 | "Direct Employer" 104 | ], 105 | "noOfVideos": 0 106 | }, 107 | { 108 | "id": 1109, 109 | "title": "Eduardo Frost Ltd", 110 | "noOfLiveJobs": 0, 111 | "shortDescription": "Best recruiter in town", 112 | "recruiterLogoId": "6ca45d4e-88eb-43d7-b86e-3760bf0256fa", 113 | "employerTypes": [ 114 | "Direct Employer" 115 | ], 116 | "noOfVideos": 0 117 | }, 118 | { 119 | "id": 1206, 120 | "title": "Elsevier", 121 | "noOfLiveJobs": 1, 122 | "shortDescription": "", 123 | "recruiterLogoId": "9d7aed14-20c1-4ad4-ac31-ce5b243a8527", 124 | "employerTypes": [ 125 | "Direct Employer" 126 | ], 127 | "noOfVideos": 0 128 | }, 129 | { 130 | "id": 11, 131 | "title": "Future (Madgex Test Data)", 132 | "noOfLiveJobs": 1, 133 | "shortDescription": "As an international media business which produces over 100 magazines each month alongside 70 websites and 16 annual live events, we can offer a wealth of career opportunities for people with the right skills and passion to move our business forward.", 134 | "recruiterLogoId": "05ccb940-5db7-48eb-afd2-4f444e9f37fe", 135 | "employerTypes": [ 136 | "Direct Employer" 137 | ], 138 | "noOfVideos": 0 139 | }, 140 | { 141 | "id": 12, 142 | "title": "Google (Madgex Test Data)", 143 | "noOfLiveJobs": 3, 144 | "shortDescription": "Chances are you have a good idea of where you want to go in life. At Google, we\u2019ve designed a culture that helps you get there.", 145 | "recruiterLogoId": "9ee7f0f3-6348-4604-9a56-ba03446de12c", 146 | "employerTypes": [ 147 | "Direct Employer" 148 | ], 149 | "noOfVideos": 0 150 | }, 151 | { 152 | "id": 13, 153 | "title": "Hilton (Madgex Test Data)", 154 | "noOfLiveJobs": 1, 155 | "shortDescription": "'Only those who feel valued can truly add value.' This is the ethos behind every decision we make at Hilton. We are immensely proud of the culture of support and sense of belonging that we all feel when we work within The Hilton Family of Hotels.", 156 | "recruiterLogoId": "3e053738-f8aa-40f9-be28-d35c4a436fa8", 157 | "employerTypes": [ 158 | "Direct Employer" 159 | ], 160 | "noOfVideos": 0 161 | }, 162 | { 163 | "id": 14, 164 | "title": "HSBC Bank plc (Madgex Test Data)", 165 | "noOfLiveJobs": 2, 166 | "shortDescription": "Is a long-term career decision about waiting for the right moment to come along? Or is it about making the right move, now? Particularly in these times, we believe in making the most of every second. That's what makes us who we are today.", 167 | "recruiterLogoId": "500312f7-a429-4559-b70d-d605d8a0486a", 168 | "employerTypes": [ 169 | "Direct Employer" 170 | ], 171 | "noOfVideos": 0 172 | }, 173 | { 174 | "id": 1117, 175 | "title": "Kings College London", 176 | "noOfLiveJobs": 6, 177 | "shortDescription": "King's is one of the world's leading research and teaching universities based in the heart of London.", 178 | "recruiterLogoId": "260374d2-32db-40b4-a7c7-959bf52a03f9", 179 | "employerTypes": [ 180 | "Direct Employer" 181 | ], 182 | "noOfVideos": 0 183 | }, 184 | { 185 | "id": 1200, 186 | "title": "Liverpool Institute for Performing Arts", 187 | "noOfLiveJobs": 1, 188 | "shortDescription": "", 189 | "recruiterLogoId": "1ee66178-1492-4a5c-a0b3-adc97eaab7a6", 190 | "employerTypes": [ 191 | "Direct Employer" 192 | ], 193 | "noOfVideos": 0 194 | }, 195 | { 196 | "id": 1236, 197 | "title": "London School of Economic & Political Science", 198 | "noOfLiveJobs": 2, 199 | "shortDescription": "", 200 | "recruiterLogoId": "4a566a38-2cd6-43ad-97e0-08733cd04c13", 201 | "employerTypes": [ 202 | "Direct Employer" 203 | ], 204 | "noOfVideos": 0 205 | }, 206 | { 207 | "id": 15, 208 | "title": "Marriott Hotels (Madgex Test Data)", 209 | "noOfLiveJobs": 1, 210 | "shortDescription": "You have already found the perfect place to pursue your professional career. Ready to be challenged? Ready to achieve and be recognised? You're on the road to success and we invite you to explore our established global company full of \"people going places", 211 | "recruiterLogoId": "d26e36da-6874-4f36-a183-5a34b1463424", 212 | "employerTypes": [ 213 | "Direct Employer" 214 | ], 215 | "noOfVideos": 0 216 | }, 217 | { 218 | "id": 1255, 219 | "title": "Newman University", 220 | "noOfLiveJobs": 2, 221 | "shortDescription": "", 222 | "recruiterLogoId": "6583fa39-0812-4142-b3c6-d8662b488790", 223 | "employerTypes": [ 224 | "Direct Employer" 225 | ], 226 | "noOfVideos": 0 227 | }, 228 | { 229 | "id": 16, 230 | "title": "Oxfam (Madgex Test Data)", 231 | "noOfLiveJobs": 8, 232 | "shortDescription": "Oxfam GB employs more than 6,000 people in 80 different countries who all share the commitment to work together to end poverty and suffering.", 233 | "recruiterLogoId": "f1b42b0e-f05e-4052-884c-6e2d33ff58dc", 234 | "employerTypes": [ 235 | "Direct Employer" 236 | ], 237 | "noOfVideos": 0 238 | }, 239 | { 240 | "id": 1253, 241 | "title": "Queen Mary, University of London", 242 | "noOfLiveJobs": 1, 243 | "shortDescription": "", 244 | "recruiterLogoId": "93988ae0-66ee-4c9d-a852-bff2fd53eeeb", 245 | "employerTypes": [ 246 | "Direct Employer" 247 | ], 248 | "noOfVideos": 0 249 | }, 250 | { 251 | "id": 1208, 252 | "title": "RMIT University", 253 | "noOfLiveJobs": 4, 254 | "shortDescription": "", 255 | "recruiterLogoId": "6d6dba39-945a-478a-9347-880fc9eaa8c0", 256 | "employerTypes": [ 257 | "Direct Employer" 258 | ], 259 | "noOfVideos": 0 260 | }, 261 | { 262 | "id": 18, 263 | "title": "Sainsbury's (Madgex Test Data)", 264 | "noOfLiveJobs": 5, 265 | "shortDescription": "We're looking for talented people to support our supermarkets, convenience stores and depots around the country. With everything from HR and Finance to Marketing and Buying under one roof, whatever your area of expertise, you\u2019ll find the right role for yo", 266 | "recruiterLogoId": "310ceb04-3dda-4e89-a9ca-0bc6f0e9dba9", 267 | "employerTypes": [ 268 | "Direct Employer" 269 | ], 270 | "noOfVideos": 0 271 | }, 272 | { 273 | "id": 19, 274 | "title": "Sky (Madgex Test Data)", 275 | "noOfLiveJobs": 12, 276 | "shortDescription": "At Sky, our job is to entertain people - and already more than 17 million viewers in 8 million households across the UK tune into our world-beating entertainment.", 277 | "recruiterLogoId": "d3031aac-8566-4227-ae58-c72811cc20e6", 278 | "employerTypes": [ 279 | "Direct Employer" 280 | ], 281 | "noOfVideos": 0 282 | }, 283 | { 284 | "id": 20, 285 | "title": "Smile (Madgex Test Data)", 286 | "noOfLiveJobs": 3, 287 | "shortDescription": "As smile continues to grow, we're constantly on the look-out for the right kind of people to join our team.", 288 | "recruiterLogoId": "6ca45d4e-88eb-43d7-b86e-3760bf0256fa", 289 | "employerTypes": [ 290 | "Direct Employer" 291 | ], 292 | "noOfVideos": 0 293 | }, 294 | { 295 | "id": 1187, 296 | "title": "SOAS University of London", 297 | "noOfLiveJobs": 1, 298 | "shortDescription": "", 299 | "recruiterLogoId": "59874867-677b-4a1b-9c5c-fe33c5fd9387", 300 | "employerTypes": [ 301 | "Direct Employer" 302 | ], 303 | "noOfVideos": 0 304 | }, 305 | { 306 | "id": 1215, 307 | "title": "Teesside University", 308 | "noOfLiveJobs": 13, 309 | "shortDescription": "", 310 | "recruiterLogoId": "cb7c6707-bb9e-464b-b168-e166d4f1e3d5", 311 | "employerTypes": [ 312 | "Direct Employer" 313 | ], 314 | "noOfVideos": 0 315 | }, 316 | { 317 | "id": 1133, 318 | "title": "TES Globetrotters", 319 | "noOfLiveJobs": 0, 320 | "shortDescription": "", 321 | "recruiterLogoId": "9ee7f0f3-6348-4604-9a56-ba03446de12c", 322 | "employerTypes": [ 323 | "Direct Employer" 324 | ], 325 | "noOfVideos": 0 326 | }, 327 | { 328 | "id": 1258, 329 | "title": "The British University in Egypt", 330 | "noOfLiveJobs": 3, 331 | "shortDescription": "", 332 | "recruiterLogoId": "492dc557-44f7-46c8-83f8-5ba20eaf9f2a", 333 | "employerTypes": [ 334 | "Direct Employer" 335 | ], 336 | "noOfVideos": 0 337 | } 338 | ] 339 | } 340 | } -------------------------------------------------------------------------------- /test/soap-api-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var madgex = require('../index.js') 4 | var assert = require('assert') 5 | var fs = require('fs') 6 | var path = require('path') 7 | var nock = require('nock') 8 | var cheerio = require ('cheerio') 9 | 10 | 11 | describe('Madgex SOAP API', function() { 12 | this.timeout(5000) 13 | 14 | var client = madgex.createClient('http://guidesmiths-webservice.madgexjbtest.com', { 15 | key: "madgex-test-key", 16 | secret: "madgex-test-secret" 17 | }).soapApi 18 | 19 | describe('Billing API', function() { 20 | 21 | it('should report errors', function(done) { 22 | 23 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 24 | .post('/billing.asmx') 25 | .replyWithError('Test Error') 26 | 27 | client.billingApi.getCategories(function(err, results) { 28 | assert.ok(err) 29 | assert.equal(err.message, 'POST http://guidesmiths-webservice.madgexjbtest.com/billing.asmx failed. Original error was: Test Error') 30 | done() 31 | }) 32 | }) 33 | 34 | it('should report HTTP failures', function(done) { 35 | 36 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 37 | .post('/billing.asmx') 38 | .reply(503) 39 | 40 | client.billingApi.getCategories(function(err, results) { 41 | assert.ok(err) 42 | assert.equal(err.message, 'POST http://guidesmiths-webservice.madgexjbtest.com/billing.asmx failed. Status code was: 503') 43 | done() 44 | }) 45 | }) 46 | 47 | it('should report SOAP failures', function(done) { 48 | 49 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 50 | .post('/billing.asmx') 51 | .replyWithFile(400, __dirname + '/replies/soap/SoapFault.xml') 52 | 53 | client.billingApi.getCategories(function(err, results) { 54 | assert.ok(err) 55 | assert.equal(err.message, 'POST http://guidesmiths-webservice.madgexjbtest.com/billing.asmx failed. Status code was: 400') 56 | assert.equal(err.statusCode, 400) 57 | assert.equal(err.faultCode, 'soap:Server') 58 | assert.ok(/System.Web.Services.Protocols.SoapException: Server was unable to process request/.test(err.faultString)) 59 | done() 60 | }) 61 | }) 62 | 63 | 64 | 65 | describe('GetCategories', function() { 66 | it('should get a list of categories', function(done) { 67 | 68 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 69 | .post('/billing.asmx') 70 | .replyWithFile(200, __dirname + '/replies/soap/GetCategories.many.xml') 71 | 72 | client.billingApi.getCategories(function(err, results) { 73 | assert.ifError(err) 74 | assert.equal(results.length, 6) 75 | assert.equal(results[0].mandatory, false) 76 | assert.equal(results[0].multiSelect, true) 77 | assert.equal(results[0].id, 100) 78 | assert.equal(results[0].name, 'Academic Discipline') 79 | done() 80 | }) 81 | }) 82 | 83 | it('should render single categores as a list', function(done) { 84 | 85 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 86 | .post('/billing.asmx') 87 | .replyWithFile(200, __dirname + '/replies/soap/GetCategories.single.xml') 88 | 89 | client.billingApi.getCategories(function(err, results) { 90 | assert.ifError(err) 91 | assert.equal(results.length, 1) 92 | assert.equal(results[0].mandatory, false) 93 | assert.equal(results[0].multiSelect, true) 94 | assert.equal(results[0].id, 105) 95 | assert.equal(results[0].name, 'Hours') 96 | done() 97 | }) 98 | }) 99 | 100 | it('should survive bad responses', function(done) { 101 | 102 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 103 | .post('/billing.asmx') 104 | .reply(200, 'not xml mwahahaha') 105 | 106 | client.billingApi.getCategories(function(err, results) { 107 | assert.ok(err) 108 | assert.equal(err.message, 'Non-whitespace before first tag.\nLine: 0\nColumn: 1\nChar: n') 109 | done() 110 | }) 111 | }) 112 | }) 113 | 114 | describe('GetCategoryTerms', function() { 115 | 116 | it('should get a list of category terms', function(done) { 117 | 118 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 119 | .post('/billing.asmx') 120 | .replyWithFile(200, __dirname + '/replies/soap/GetCategoryTerms.many.xml') 121 | 122 | client.billingApi.getCategoryTerms({categoryId: 100}, function(err, results) { 123 | assert.ifError(err) 124 | assert.equal(results.length, 21) 125 | assert.equal(results[0].id, 1) 126 | assert.equal(results[0].name, 'Agriculture, Food and Veterinary') 127 | done() 128 | }) 129 | }) 130 | 131 | 132 | it('should render single category terms as a list', function(done) { 133 | 134 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 135 | .post('/billing.asmx') 136 | .replyWithFile(200, __dirname + '/replies/soap/GetCategoryTerms.single.xml') 137 | 138 | client.billingApi.getCategoryTerms({categoryId: 100}, function(err, results) { 139 | assert.ifError(err) 140 | assert.equal(results.length, 1) 141 | assert.equal(results[0].id, 21) 142 | assert.equal(results[0].name, 'Sport and Leisure') 143 | done() 144 | }) 145 | }) 146 | }) 147 | 148 | describe('GetLocations', function() { 149 | 150 | it('should get a list of locations', function(done) { 151 | 152 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 153 | .post('/billing.asmx') 154 | .reply(function(uri, requestBody) { 155 | var $ = cheerio.load(requestBody, { xmlMode: true }) 156 | assert.equal($('job\\:searchPrefix').text(), 'Lon') 157 | return fs.createReadStream(__dirname + '/replies/soap/GetLocations.many.xml') 158 | }) 159 | 160 | client.billingApi.getLocations({prefix: 'Lon'}, function(err, results) { 161 | assert.ifError(err) 162 | assert.equal(results.length, 20) 163 | assert.equal(results[0].locationId, 325) 164 | assert.equal(results[0].label, 'London (Greater)') 165 | done() 166 | }) 167 | }) 168 | 169 | it('should render single location as a list', function(done) { 170 | 171 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 172 | .post('/billing.asmx') 173 | .replyWithFile(200, __dirname + '/replies/soap/GetLocations.single.xml') 174 | 175 | client.billingApi.getLocations({prefix: 'Longney'}, function(err, results) { 176 | assert.ifError(err) 177 | assert.equal(results.length, 1) 178 | assert.equal(results[0].locationId, 17116) 179 | assert.equal(results[0].label, 'Longney, Gloucester') 180 | done() 181 | }) 182 | }) 183 | }) 184 | 185 | describe('AddBilledJob', function() { 186 | 187 | it('should add a billed job', function(done) { 188 | 189 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 190 | .post('/billing.asmx') 191 | .reply(function(uri, requestBody) { 192 | var $ = cheerio.load(requestBody, { xmlMode: true }) 193 | assert.equal($('job\\:sRecruiterBillingID').text(), 'rec-1') 194 | assert.equal($('job\\:sEmployerBillingID').text(), 'emp-1') 195 | assert.equal($('job\\:sJobID').text(), 'job-1') 196 | assert.equal($('job\\:sStartDateTime').text(), '13/01/2015 10:35') 197 | assert.equal($('job\\:sEndDateTime').text(), '14/01/2025 14:55') 198 | assert.equal($('job\\:sApplicationMethod').text(), 'ExternalRedirect') 199 | assert.equal($('job\\:sApplicationEmail').text(), 'foo@example.com') 200 | assert.equal($('job\\:sExternalApplicationURL').text(), 'http://www.example.com/jobzRus') 201 | assert.equal($('job\\:jobProperties job\\:WSJobPropertyValue').length, 2) 202 | assert.equal($('job\\:jobProperties job\\:WSJobPropertyValue:nth-child(1) job\\:Name').text(), 'JobTitle') 203 | assert.equal($('job\\:jobProperties job\\:WSJobPropertyValue:nth-child(1) job\\:Value').text(), 'job-title') 204 | assert.equal($('job\\:jobProperties job\\:WSJobPropertyValue:nth-child(2) job\\:Name').text(), 'JobDescription') 205 | assert.equal($('job\\:jobProperties job\\:WSJobPropertyValue:nth-child(2) job\\:Value').text(), 'job-description') 206 | 207 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms').length, 2) 208 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms:nth-child(1) job\\:CategoryID').text(), '100') 209 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms:nth-child(1) job\\:TermIDs job\\:int').length, 3) 210 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms:nth-child(1) job\\:TermIDs job\\:int:nth-child(1)').text(), '1') 211 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms:nth-child(1) job\\:TermIDs job\\:int:nth-child(3)').text(), '3') 212 | 213 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms:nth-child(2) job\\:CategoryID').text(), '101') 214 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms:nth-child(2) job\\:TermIDs job\\:int').length, 3) 215 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms:nth-child(2) job\\:TermIDs job\\:int:nth-child(1)').text(), '4') 216 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms:nth-child(2) job\\:TermIDs job\\:int:nth-child(3)').text(), '6') 217 | 218 | assert.equal($('job\\:jobUpsells job\\:WSUpsell').length, 2) 219 | assert.equal($('job\\:jobUpsells job\\:WSUpsell:nth-child(1) job\\:ID').text(), '1') 220 | assert.equal($('job\\:jobUpsells job\\:WSUpsell:nth-child(1) job\\:Name').text(), 'Top Jobs') 221 | 222 | assert.equal($('job\\:liProductID').text(), 'product-id') 223 | assert.equal($('job\\:liPriceSoldAt').text(), '100') 224 | assert.equal($('job\\:isBackFill').text(), 'true') 225 | return fs.createReadStream(__dirname + '/replies/soap/AddBilledJob.ok.xml') 226 | }) 227 | 228 | client.billingApi.addBilledJob({ 229 | id: 'job-1', 230 | recruiterBillingId: 'rec-1', 231 | employerBillingId: 'emp-1', 232 | startDateTime: '13/01/2015 10:35', 233 | endDateTime: '14/01/2025 14:55', 234 | applicationMethod: 'ExternalRedirect', 235 | applicationEmail: 'foo@example.com', 236 | externalApplicationUrl: 'http://www.example.com/jobzRus', 237 | properties: { 238 | JobTitle: 'job-title', 239 | JobDescription: 'job-description' 240 | }, 241 | categorization: { 242 | terms: [ 243 | { 244 | categoryId: 100, 245 | termIds: [1, 2, 3] 246 | }, 247 | { 248 | categoryId: 101, 249 | termIds: [4, 5, 6] 250 | } 251 | ] 252 | }, 253 | upsells: [ 254 | { 255 | id: '1', 256 | name: 'Top Jobs' 257 | }, 258 | { 259 | id: '2' 260 | } 261 | ], 262 | productId: 'product-id', 263 | priceSoldAt: 100, 264 | isBackFill: true 265 | }, function(err, result) { 266 | assert.ifError(err) 267 | assert.equal(result, 1340) 268 | done() 269 | }) 270 | }) 271 | 272 | it('should omit optional elements when not specified', function(done) { 273 | 274 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 275 | .post('/billing.asmx') 276 | .reply(function(uri, requestBody) { 277 | var $ = cheerio.load(requestBody, { xmlMode: true }) 278 | assert.equal($('job\\:sStartDateTime').length, 0) 279 | assert.equal($('job\\:sEndDateTime').length, 0) 280 | assert.equal($('job\\:sApplicationMethod').length, 0) 281 | assert.equal($('job\\:sApplicationEmail').length, 0) 282 | assert.equal($('job\\:sExternalApplicationURL').length, 0) 283 | assert.equal($('job\\:jobProperties').length, 1) 284 | assert.equal($('job\\:jobProperties job\\:WSJobPropertyValue').length, 0) 285 | assert.equal($('job\\:jobCategorization').length, 1) 286 | assert.equal($('job\\:jobCategorization job\\:WSSelectedTerms').length, 0) 287 | assert.equal($('job\\:jobUpsells').length, 0) 288 | return fs.createReadStream(__dirname + '/replies/soap/AddBilledJob.ok.xml') 289 | }) 290 | 291 | client.billingApi.addBilledJob({ 292 | id: 'job-1', 293 | recruiterBillingId: 'rec-1', 294 | employerBillingId: 'emp-1' 295 | }, function(err, results) { 296 | assert.ifError(err) 297 | done() 298 | }) 299 | }) 300 | 301 | it('should use xsi:nil for certain elements with minOcccurs=1 mandatory=false when not specified', function(done) { 302 | 303 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 304 | .post('/billing.asmx') 305 | .reply(function(uri, requestBody) { 306 | var $ = cheerio.load(requestBody, { xmlMode: true }) 307 | assert.equal($('job\\:liProductID').length, 1) 308 | assert.equal($('job\\:liProductID').attr('xsi:nil'), 'true') 309 | assert.equal($('job\\:liPriceSoldAt').length, 1) 310 | assert.equal($('job\\:liPriceSoldAt').attr('xsi:nil'), 'true') 311 | assert.equal($('job\\:isBackFill').length, 1) 312 | assert.equal($('job\\:isBackFill').attr('xsi:nil'), 'true') 313 | return fs.createReadStream(__dirname + '/replies/soap/AddBilledJob.ok.xml') 314 | }) 315 | 316 | client.billingApi.addBilledJob({ 317 | id: 'job-1', 318 | recruiterBillingId: 'rec-1', 319 | employerBillingId: 'emp-1' 320 | }, function(err, results) { 321 | assert.ifError(err) 322 | done() 323 | }) 324 | }) 325 | }) 326 | 327 | describe('UpdateBilledJob', function() { 328 | 329 | it('should update a billed job', function(done) { 330 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 331 | .post('/billing.asmx') 332 | .reply(function(uri, requestBody) { 333 | var $ = cheerio.load(requestBody, { xmlMode: true }) 334 | assert.equal($('job\\:sRecruiterBillingID').text(), 'rec-1') 335 | assert.equal($('job\\:sEmployerBillingID').text(), 'emp-1') 336 | assert.equal($('job\\:sJobID').text(), 'job-1') 337 | return fs.createReadStream(__dirname + '/replies/soap/UpdateBilledJob.ok.xml') 338 | }) 339 | 340 | client.billingApi.updateBilledJob({ 341 | id: 'job-1', 342 | recruiterBillingId: 'rec-1', 343 | employerBillingId: 'emp-1', 344 | startDateTime: '13/01/2015 10:35', 345 | endDateTime: '14/01/2025 14:55', 346 | applicationMethod: 'ExternalRedirect', 347 | applicationEmail: 'foo@example.com', 348 | externalApplicationUrl: 'http://www.example.com/jobzRus', 349 | properties: { 350 | JobTitle: 'job-title', 351 | JobDescription: 'job-description' 352 | }, 353 | categorization: { 354 | terms: [ 355 | { 356 | categoryId: 100, 357 | termIds: [1, 2, 3] 358 | }, 359 | { 360 | categoryId: 101, 361 | termIds: [4, 5, 6] 362 | } 363 | ] 364 | }, 365 | productId: 'product-id', 366 | priceSoldAt: 100, 367 | isBackFill: true 368 | }, function(err, result) { 369 | assert.ifError(err) 370 | assert.equal(result, 1340) 371 | done() 372 | }) 373 | }) 374 | }) 375 | 376 | 377 | describe('AddRecruiterV2', function() { 378 | 379 | it('should add recruiter', function(done) { 380 | 381 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 382 | .post('/billing.asmx') 383 | .reply(function(uri, requestBody) { 384 | var $ = cheerio.load(requestBody, { xmlMode: true }) 385 | assert.equal($('job\\:RecruiterName').text(), 'recruiter-name') 386 | assert.equal($('job\\:CustomerBillingID').text(), 'cust-billing-id') 387 | assert.equal($('job\\:ContactFirstName').text(), 'contact-fn') 388 | assert.equal($('job\\:ContactLastName').text(), 'contact-ln-1') 389 | assert.equal($('job\\:ContactLastName2').text(), 'contact-ln-2') 390 | assert.equal($('job\\:Address1').text(), 'address-1') 391 | assert.equal($('job\\:Address2').text(), 'address-2') 392 | assert.equal($('job\\:Address3').text(), 'address-3') 393 | assert.equal($('job\\:TownCity').text(), 'town') 394 | assert.equal($('job\\:CountyState').text(), 'county') 395 | assert.equal($('job\\:ZipPostCode').text(), 'zip') 396 | assert.equal($('job\\:Country').text(), 'country') 397 | assert.equal($('job\\:Telephone').text(), 'phone') 398 | assert.equal($('job\\:ContactEmail').text(), 'contact@example.com') 399 | assert.equal($('job\\:RecruiterTypeID').text(), 1) 400 | assert.equal($('job\\:Image job\\:AssetID').text(), 'image-asset-id') 401 | assert.equal($('job\\:Image job\\:Base64BlobString').text(), 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4z8BQDwAEgAF/posBPQAAAABJRU5ErkJggg==') 402 | assert.equal($('job\\:EnhancedJobHtml job\\:JobTitleTextCss').text(), '#000') 403 | assert.equal($('job\\:EnhancedJobHtml job\\:JobHeadingsTextCss').text(), '#111') 404 | assert.equal($('job\\:EnhancedJobHtml job\\:ButtonBackgroundCss').text(), '#222') 405 | assert.equal($('job\\:EnhancedJobHtml job\\:ButtonTextCss').text(), '#333') 406 | assert.equal($('job\\:EnhancedJobHtml job\\:HyperLinkCss').text(), '#444') 407 | assert.equal($('job\\:EnhancedJobHtml job\\:Banner job\\:AssetID').text(), 'banner-asset-id') 408 | assert.equal($('job\\:EnhancedJobHtml job\\:Banner job\\:Base64BlobString').text(), 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4z8BQDwAEgAF/posBPQAAAABJRU5ErkJggg==') 409 | assert.equal($('job\\:AccountManager job\\:EmailAddress').text(), 'manager@example.com') 410 | assert.equal($('job\\:AccountManager job\\:FirstName').text(), 'manager-fn') 411 | assert.equal($('job\\:AccountManager job\\:LastName').text(), 'manager-ln') 412 | assert.equal($('job\\:CanAccessCvDatabase').text(), 'true') 413 | 414 | return fs.createReadStream(__dirname + '/replies/soap/AddRecruiterV2.ok.xml') 415 | }) 416 | 417 | client.billingApi.addRecruiterV2({ 418 | recruiterName: 'recruiter-name', 419 | customerBillingID: 'cust-billing-id', 420 | contactFirstName: 'contact-fn', 421 | contactLastName: 'contact-ln-1', 422 | contactLastName2: 'contact-ln-2', 423 | address1: 'address-1', 424 | address2: 'address-2', 425 | address3: 'address-3', 426 | townCity: 'town', 427 | countyState: 'county', 428 | zipPostCode: 'zip', 429 | country: 'country', 430 | telephone: 'phone', 431 | contactEmail: 'contact@example.com', 432 | recruiterTypeID: 1, 433 | image: { 434 | assetId: 'image-asset-id', 435 | base64BlobString: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4z8BQDwAEgAF/posBPQAAAABJRU5ErkJggg==' 436 | }, 437 | enhancedJobHtml: { 438 | jobTitleTextCss: '#000', 439 | jobHeadingsTextCss: '#111', 440 | buttonBackgroundCss: '#222', 441 | buttonTextCss: '#333', 442 | hyperLinkCss: '#444', 443 | banner: { 444 | assetId: 'banner-asset-id', 445 | base64BlobString: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4z8BQDwAEgAF/posBPQAAAABJRU5ErkJggg==' 446 | } 447 | }, 448 | accountManager: { 449 | emailAddress: 'manager@example.com', 450 | firstName: 'manager-fn', 451 | lastName: 'manager-ln', 452 | }, 453 | canAccessCvDatabase: true 454 | }, function(err, result) { 455 | assert.ifError(err) 456 | assert.equal(result, 1339) 457 | done() 458 | }) 459 | }) 460 | }) 461 | 462 | describe('UpdateRecruiterWithBillingID', function() { 463 | 464 | it('should update recruiter', function(done) { 465 | 466 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 467 | .post('/billing.asmx') 468 | .reply(function(uri, requestBody) { 469 | var $ = cheerio.load(requestBody, { xmlMode: true }) 470 | assert.equal($('job\\:RecruiterName').text(), 'recruiter-name') 471 | assert.equal($('job\\:CustomerBillingID').text(), 'cust-billing-id') 472 | assert.equal($('job\\:ContactFirstName').text(), 'contact-fn') 473 | assert.equal($('job\\:ContactLastName').text(), 'contact-ln-1') 474 | assert.equal($('job\\:ContactLastName2').text(), 'contact-ln-2') 475 | assert.equal($('job\\:Address1').text(), 'address-1') 476 | assert.equal($('job\\:Address2').text(), 'address-2') 477 | assert.equal($('job\\:Address3').text(), 'address-3') 478 | assert.equal($('job\\:TownCity').text(), 'town') 479 | assert.equal($('job\\:CountyState').text(), 'county') 480 | assert.equal($('job\\:ZipPostCode').text(), 'zip') 481 | assert.equal($('job\\:Country').text(), 'country') 482 | assert.equal($('job\\:Telephone').text(), 'phone') 483 | assert.equal($('job\\:ContactEmail').text(), 'contact@example.com') 484 | assert.equal($('job\\:RecruiterTypeID').text(), 1) 485 | assert.equal($('job\\:Image job\\:AssetID').text(), 'image-asset-id') 486 | assert.equal($('job\\:Image job\\:Base64BlobString').text(), 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4z8BQDwAEgAF/posBPQAAAABJRU5ErkJggg==') 487 | assert.equal($('job\\:EnhancedJobHtml job\\:JobTitleTextCss').text(), '#000') 488 | assert.equal($('job\\:EnhancedJobHtml job\\:JobHeadingsTextCss').text(), '#111') 489 | assert.equal($('job\\:EnhancedJobHtml job\\:ButtonBackgroundCss').text(), '#222') 490 | assert.equal($('job\\:EnhancedJobHtml job\\:ButtonTextCss').text(), '#333') 491 | assert.equal($('job\\:EnhancedJobHtml job\\:HyperLinkCss').text(), '#444') 492 | assert.equal($('job\\:EnhancedJobHtml job\\:Banner job\\:AssetID').text(), 'banner-asset-id') 493 | assert.equal($('job\\:EnhancedJobHtml job\\:Banner job\\:Base64BlobString').text(), 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4z8BQDwAEgAF/posBPQAAAABJRU5ErkJggg==') 494 | assert.equal($('job\\:AccountManager job\\:EmailAddress').text(), 'manager@example.com') 495 | assert.equal($('job\\:AccountManager job\\:FirstName').text(), 'manager-fn') 496 | assert.equal($('job\\:AccountManager job\\:LastName').text(), 'manager-ln') 497 | assert.equal($('job\\:CanAccessCvDatabase').text(), 'true') 498 | 499 | return fs.createReadStream(__dirname + '/replies/soap/UpdateRecruiterWithBillingID.ok.xml') 500 | }) 501 | 502 | client.billingApi.updateRecruiterWithBillingId({ 503 | recruiterName: 'recruiter-name', 504 | customerBillingID: 'cust-billing-id', 505 | contactFirstName: 'contact-fn', 506 | contactLastName: 'contact-ln-1', 507 | contactLastName2: 'contact-ln-2', 508 | address1: 'address-1', 509 | address2: 'address-2', 510 | address3: 'address-3', 511 | townCity: 'town', 512 | countyState: 'county', 513 | zipPostCode: 'zip', 514 | country: 'country', 515 | telephone: 'phone', 516 | contactEmail: 'contact@example.com', 517 | recruiterTypeID: 1, 518 | image: { 519 | assetId: 'image-asset-id', 520 | base64BlobString: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4z8BQDwAEgAF/posBPQAAAABJRU5ErkJggg==' 521 | }, 522 | enhancedJobHtml: { 523 | jobTitleTextCss: '#000', 524 | jobHeadingsTextCss: '#111', 525 | buttonBackgroundCss: '#222', 526 | buttonTextCss: '#333', 527 | hyperLinkCss: '#444', 528 | banner: { 529 | assetId: 'banner-asset-id', 530 | base64BlobString: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4z8BQDwAEgAF/posBPQAAAABJRU5ErkJggg==' 531 | } 532 | }, 533 | accountManager: { 534 | emailAddress: 'manager@example.com', 535 | firstName: 'manager-fn', 536 | lastName: 'manager-ln', 537 | }, 538 | canAccessCvDatabase: true 539 | }, function(err, result) { 540 | assert.ifError(err) 541 | assert.equal(result, 1339) 542 | done() 543 | }) 544 | }) 545 | }) 546 | 547 | describe('CheckRecruiterExistsV2', function() { 548 | 549 | it('should find a Recruiter by madgex id', function(done) { 550 | 551 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 552 | .post('/billing.asmx') 553 | .replyWithFile(200, __dirname + '/replies/soap/CheckRecruiterExistsV2.single.xml') 554 | 555 | client.billingApi.checkRecruiterExistsV2({sRecruiterName: '', sCustomerBillingId: '', sMadgexID: '11713'}, function(err, results) { 556 | assert.ifError(err) 557 | assert.equal(results.exists, true) 558 | assert.equal(results.billingId, 23005869) 559 | assert.equal(results.id, 11713) 560 | done() 561 | }) 562 | }) 563 | }) 564 | 565 | describe('CheckRecruiterExistsV2', function() { 566 | 567 | it('a Recruiter by customer billing id', function(done) { 568 | 569 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 570 | .post('/billing.asmx') 571 | .replyWithFile(200, __dirname + '/replies/soap/CheckRecruiterExistsV2.single.xml') 572 | 573 | client.billingApi.checkRecruiterExistsV2({sRecruiterName: '', sCustomerBillingId: '23005869', sMadgexID: ''}, function(err, results) { 574 | assert.ifError(err) 575 | assert.equal(results.exists, true) 576 | assert.equal(results.billingId, 23005869) 577 | assert.equal(results.id, 11713) 578 | done() 579 | }) 580 | }) 581 | }) 582 | 583 | describe('CheckRecruiterExistsV2 with promises', function() { 584 | 585 | it('resolve with promise - a Recruiter by customer billing id', function(done) { 586 | 587 | var scope = nock('http://guidesmiths-webservice.madgexjbtest.com') 588 | .post('/billing.asmx') 589 | .replyWithFile(200, __dirname + '/replies/soap/CheckRecruiterExistsV2.single.xml') 590 | 591 | client.billingApi.checkRecruiterExistsV2({sRecruiterName: '', sCustomerBillingId: '23005869', sMadgexID: ''}) 592 | .then(function (results) { 593 | assert.equal(results.exists, true) 594 | assert.equal(results.billingId, 23005869) 595 | assert.equal(results.id, 11713) 596 | done() 597 | }, function (err) { 598 | assert.ifError(err) 599 | }) 600 | }) 601 | }) 602 | }) 603 | }) 604 | 605 | 606 | -------------------------------------------------------------------------------- /test/replies/rest/get.jobinfo.search.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "result": { 4 | "uriTemplate": "http:\/\/guidesmiths-web.madgexjbtest.com\/job\/{jobId}\/?trackid=22", 5 | "totalResults": 132, 6 | "startIndex": 0, 7 | "itemsPerPage": 30, 8 | "jobs": [ 9 | { 10 | "id": 1131, 11 | "title": "Inbound Sales Advisor", 12 | "posted": "21 Apr 2015", 13 | "description": "", 14 | "recruiter": "Transport for London (Madgex Test Data)", 15 | "recruiterID": 21, 16 | "recruiterLogo": "8bc4bed9-0fc3-42b3-a3a0-920e195f8ca4", 17 | "salary": "\u00a330,001 - \u00a340,000", 18 | "location": "ME2 4JD, Rochester", 19 | "geo": [ 20 | 21 | ], 22 | "jobRole": null, 23 | "applyInfo": { 24 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1131\/inbound-sales-advisor\/?trackid=22", 25 | "type": "email-applications", 26 | "allowMobileExternalRedirects": true 27 | }, 28 | "upsells": [ 29 | 30 | ], 31 | "noOfVideos": 0, 32 | "isPremiumJob": false, 33 | "isHomePageJob": false, 34 | "isDisplayLogoJob": false, 35 | "isTopJob": false, 36 | "categories": { 37 | "function": [ 38 | "Academic Related", 39 | [ 40 | "Professional Services", 41 | [ 42 | "Administrative", 43 | "Finance and Procurement" 44 | ] 45 | ] 46 | ], 47 | "location": [ 48 | 49 | ] 50 | }, 51 | "urlId": "1131\/inbound-sales-advisor", 52 | "usesAltRecruiterName": false 53 | }, 54 | { 55 | "id": 1124, 56 | "title": "Vice Chancellor", 57 | "posted": "21 Apr 2015", 58 | "description": "", 59 | "recruiter": "Kings College London", 60 | "recruiterID": 1117, 61 | "recruiterLogo": "260374d2-32db-40b4-a7c7-959bf52a03f9", 62 | "salary": "Competitive", 63 | "location": "London (Central), London (Greater)", 64 | "geo": [ 65 | 66 | ], 67 | "jobRole": null, 68 | "applyInfo": { 69 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1124\/vice-chancellor\/?trackid=22", 70 | "type": "response-management", 71 | "allowMobileExternalRedirects": true, 72 | "externalApplicationUrl": "http:\/\/www.kcl.ac.uk\/hr\/jobs\/principal.aspx" 73 | }, 74 | "upsells": [ 75 | 5, 76 | 6, 77 | 7 78 | ], 79 | "noOfVideos": 0, 80 | "isPremiumJob": true, 81 | "isHomePageJob": false, 82 | "isDisplayLogoJob": true, 83 | "isTopJob": false, 84 | "categories": { 85 | "function": [ 86 | [ 87 | "Senior Management & Heads of Department", 88 | [ 89 | "Vice-chancellors \/ Principals \/ Provosts", 90 | "Deputy Vice-chancellors", 91 | "Pro Vice-chancellors" 92 | ] 93 | ] 94 | ], 95 | "location": [ 96 | 97 | ] 98 | }, 99 | "urlId": "1124\/vice-chancellor", 100 | "usesAltRecruiterName": false 101 | }, 102 | { 103 | "id": 1123, 104 | "title": "Lecturer in Computer Science", 105 | "posted": "21 Apr 2015", 106 | "description": "", 107 | "recruiter": "Kings College London", 108 | "recruiterID": 1117, 109 | "recruiterLogo": "260374d2-32db-40b4-a7c7-959bf52a03f9", 110 | "salary": "\u00a335,000 - \u00a340,000", 111 | "location": "London (Central), London (Greater)", 112 | "geo": [ 113 | 114 | ], 115 | "jobRole": null, 116 | "applyInfo": { 117 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1123\/lecturer-in-computer-science\/?trackid=22", 118 | "type": "response-management", 119 | "allowMobileExternalRedirects": true, 120 | "externalApplicationUrl": "http:\/\/www.kcl.ac.uk\/hr\/jobs\/principal.aspx" 121 | }, 122 | "upsells": [ 123 | 124 | ], 125 | "noOfVideos": 0, 126 | "isPremiumJob": false, 127 | "isHomePageJob": false, 128 | "isDisplayLogoJob": true, 129 | "isTopJob": false, 130 | "categories": { 131 | "function": [ 132 | [ 133 | "Academic Posts", 134 | [ 135 | "Lecturers \/ Assistant Professors" 136 | ] 137 | ] 138 | ], 139 | "location": [ 140 | 141 | ] 142 | }, 143 | "urlId": "1123\/lecturer-in-computer-science", 144 | "usesAltRecruiterName": false 145 | }, 146 | { 147 | "id": 1122, 148 | "title": "Head of Humantities", 149 | "posted": "21 Apr 2015", 150 | "description": "", 151 | "recruiter": "Kings College London", 152 | "recruiterID": 1117, 153 | "recruiterLogo": "260374d2-32db-40b4-a7c7-959bf52a03f9", 154 | "salary": "\u00a380,000 - \u00a380,001", 155 | "location": "London (Central), London (Greater)", 156 | "geo": [ 157 | 158 | ], 159 | "jobRole": null, 160 | "applyInfo": { 161 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1122\/head-of-humantities\/?trackid=22", 162 | "type": "response-management", 163 | "allowMobileExternalRedirects": true, 164 | "externalApplicationUrl": "http:\/\/www.kcl.ac.uk\/hr\/jobs\/principal.aspx" 165 | }, 166 | "upsells": [ 167 | 5 168 | ], 169 | "noOfVideos": 0, 170 | "isPremiumJob": true, 171 | "isHomePageJob": false, 172 | "isDisplayLogoJob": true, 173 | "isTopJob": false, 174 | "categories": { 175 | "function": [ 176 | [ 177 | "Senior Management & Heads of Department", 178 | [ 179 | "Heads of Department" 180 | ] 181 | ] 182 | ], 183 | "location": [ 184 | 185 | ] 186 | }, 187 | "urlId": "1122\/head-of-humantities", 188 | "usesAltRecruiterName": false 189 | }, 190 | { 191 | "id": 1261, 192 | "title": "Senior Lecturer Appointment ", 193 | "posted": "21 Apr 2015", 194 | "description": "", 195 | "recruiter": "The British University in Egypt", 196 | "recruiterID": 1258, 197 | "recruiterLogo": "492dc557-44f7-46c8-83f8-5ba20eaf9f2a", 198 | "salary": "", 199 | "location": "Egypt (EG)", 200 | "geo": [ 201 | 202 | ], 203 | "jobRole": null, 204 | "applyInfo": { 205 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1261\/senior-lecturer-appointment-\/?trackid=22", 206 | "type": "email-applications", 207 | "allowMobileExternalRedirects": true 208 | }, 209 | "upsells": [ 210 | 6 211 | ], 212 | "noOfVideos": 0, 213 | "isPremiumJob": false, 214 | "isHomePageJob": false, 215 | "isDisplayLogoJob": true, 216 | "isTopJob": false, 217 | "categories": { 218 | "function": [ 219 | [ 220 | "Academic Posts", 221 | [ 222 | "Principal \/ Senior Lecturers \/ Associate Professors" 223 | ] 224 | ] 225 | ], 226 | "location": [ 227 | 228 | ] 229 | }, 230 | "urlId": "1261\/senior-lecturer-appointment-", 231 | "usesAltRecruiterName": false 232 | }, 233 | { 234 | "id": 1237, 235 | "title": "Academic Registrar", 236 | "posted": "21 Apr 2015", 237 | "description": "", 238 | "recruiter": "London School of Economic & Political Science", 239 | "recruiterID": 1236, 240 | "recruiterLogo": "4a566a38-2cd6-43ad-97e0-08733cd04c13", 241 | "salary": "", 242 | "location": "London (Greater)", 243 | "geo": [ 244 | 245 | ], 246 | "jobRole": null, 247 | "applyInfo": { 248 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1237\/academic-registrar\/?trackid=22", 249 | "type": "email-applications", 250 | "allowMobileExternalRedirects": true 251 | }, 252 | "upsells": [ 253 | 6, 254 | 5 255 | ], 256 | "noOfVideos": 0, 257 | "isPremiumJob": false, 258 | "isHomePageJob": false, 259 | "isDisplayLogoJob": true, 260 | "isTopJob": false, 261 | "categories": { 262 | "function": [ 263 | [ 264 | "Senior Management & Heads of Department", 265 | [ 266 | "Registrars \/ Assistant Registrars" 267 | ] 268 | ] 269 | ], 270 | "location": [ 271 | 272 | ] 273 | }, 274 | "urlId": "1237\/academic-registrar", 275 | "usesAltRecruiterName": false 276 | }, 277 | { 278 | "id": 1235, 279 | "title": "Vice-Chancellor", 280 | "posted": "21 Apr 2015", 281 | "description": "", 282 | "recruiter": "University of Warwick", 283 | "recruiterID": 1234, 284 | "recruiterLogo": "336b72c0-20f3-4961-90fe-ea706ff7215c", 285 | "salary": "", 286 | "location": "Warwick, Warwickshire", 287 | "geo": [ 288 | 289 | ], 290 | "jobRole": null, 291 | "applyInfo": { 292 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1235\/vice-chancellor\/?trackid=22", 293 | "type": "external-redirect", 294 | "allowMobileExternalRedirects": true, 295 | "externalApplicationUrl": "http:\/\/www.perrettlaver.com\/candidates" 296 | }, 297 | "upsells": [ 298 | 5, 299 | 6 300 | ], 301 | "noOfVideos": 0, 302 | "isPremiumJob": false, 303 | "isHomePageJob": false, 304 | "isDisplayLogoJob": true, 305 | "isTopJob": false, 306 | "categories": { 307 | "function": [ 308 | [ 309 | "Senior Management & Heads of Department", 310 | [ 311 | "Vice-chancellors \/ Principals \/ Provosts" 312 | ] 313 | ] 314 | ], 315 | "location": [ 316 | 317 | ] 318 | }, 319 | "urlId": "1235\/vice-chancellor", 320 | "usesAltRecruiterName": false 321 | }, 322 | { 323 | "id": 1233, 324 | "title": "Lecturer\/Senior Lecturer Posts in Disruptive Technologies", 325 | "posted": "21 Apr 2015", 326 | "description": "", 327 | "recruiter": "University of Greenwich", 328 | "recruiterID": 1229, 329 | "recruiterLogo": "69beea65-72a0-4dde-b5e2-0122b9e4532c", 330 | "salary": "\u00a331,342 to \u00a345,954 plus \u00a33,437 London weighting per annum", 331 | "location": "London (Greater)", 332 | "geo": [ 333 | 334 | ], 335 | "jobRole": null, 336 | "applyInfo": { 337 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1233\/lecturer-senior-lecturer-posts-in-disruptive-technologies\/?trackid=22", 338 | "type": "external-redirect", 339 | "allowMobileExternalRedirects": true, 340 | "externalApplicationUrl": "https:\/\/jobs.gre.ac.uk\/Vacancy.aspx?ref=840" 341 | }, 342 | "upsells": [ 343 | 8, 344 | 5, 345 | 7, 346 | 6 347 | ], 348 | "noOfVideos": 0, 349 | "isPremiumJob": false, 350 | "isHomePageJob": false, 351 | "isDisplayLogoJob": true, 352 | "isTopJob": false, 353 | "categories": { 354 | "function": [ 355 | [ 356 | "Senior Management & Heads of Department", 357 | [ 358 | "Heads of Department", 359 | "Other Senior Management" 360 | ] 361 | ] 362 | ], 363 | "location": [ 364 | 365 | ] 366 | }, 367 | "urlId": "1233\/lecturer-senior-lecturer-posts-in-disruptive-technologies", 368 | "usesAltRecruiterName": false 369 | }, 370 | { 371 | "id": 1232, 372 | "title": "Subject Lead for Film and Television", 373 | "posted": "21 Apr 2015", 374 | "description": "", 375 | "recruiter": "University of Greenwich", 376 | "recruiterID": 1229, 377 | "recruiterLogo": "69beea65-72a0-4dde-b5e2-0122b9e4532c", 378 | "salary": "\u00a347,328 to \u00a354,841 plus \u00a33,437 London weighting per annum", 379 | "location": "London (Greater)", 380 | "geo": [ 381 | 382 | ], 383 | "jobRole": null, 384 | "applyInfo": { 385 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1232\/subject-lead-for-film-and-television\/?trackid=22", 386 | "type": "external-redirect", 387 | "allowMobileExternalRedirects": true, 388 | "externalApplicationUrl": "https:\/\/jobs.gre.ac.uk\/Vacancy.aspx?ref=841" 389 | }, 390 | "upsells": [ 391 | 8, 392 | 5, 393 | 7, 394 | 6 395 | ], 396 | "noOfVideos": 0, 397 | "isPremiumJob": false, 398 | "isHomePageJob": false, 399 | "isDisplayLogoJob": true, 400 | "isTopJob": false, 401 | "categories": { 402 | "function": [ 403 | [ 404 | "Senior Management & Heads of Department", 405 | [ 406 | "Heads of Department", 407 | "Other Senior Management" 408 | ] 409 | ] 410 | ], 411 | "location": [ 412 | 413 | ] 414 | }, 415 | "urlId": "1232\/subject-lead-for-film-and-television", 416 | "usesAltRecruiterName": false 417 | }, 418 | { 419 | "id": 1263, 420 | "title": "Deputy Vice-Chancellor - Student Experience", 421 | "posted": "21 Apr 2015", 422 | "description": "", 423 | "recruiter": "University of Westminster", 424 | "recruiterID": 1262, 425 | "recruiterLogo": "f2cb4e60-d376-4655-b756-6a2d3502d620", 426 | "salary": "Competitive salary", 427 | "location": "London (Greater)", 428 | "geo": [ 429 | 430 | ], 431 | "jobRole": null, 432 | "applyInfo": { 433 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1263\/deputy-vice-chancellor-student-experience\/?trackid=22", 434 | "type": "external-redirect", 435 | "allowMobileExternalRedirects": true, 436 | "externalApplicationUrl": "http:\/\/www.perrettlaver.com\/westminster" 437 | }, 438 | "upsells": [ 439 | 6 440 | ], 441 | "noOfVideos": 0, 442 | "isPremiumJob": false, 443 | "isHomePageJob": false, 444 | "isDisplayLogoJob": true, 445 | "isTopJob": false, 446 | "categories": { 447 | "function": [ 448 | [ 449 | "Senior Management & Heads of Department", 450 | [ 451 | "Deputy Vice-chancellors" 452 | ] 453 | ] 454 | ], 455 | "location": [ 456 | 457 | ] 458 | }, 459 | "urlId": "1263\/deputy-vice-chancellor-student-experience", 460 | "usesAltRecruiterName": false 461 | }, 462 | { 463 | "id": 1231, 464 | "title": "Deputy Head of Department, Creative Professions and Digital Arts", 465 | "posted": "21 Apr 2015", 466 | "description": "", 467 | "recruiter": "University of Greenwich", 468 | "recruiterID": 1229, 469 | "recruiterLogo": "69beea65-72a0-4dde-b5e2-0122b9e4532c", 470 | "salary": "\u00a347,328 to \u00a354,841 plus \u00a33,437 London weighting per annum", 471 | "location": "London (Greater)", 472 | "geo": [ 473 | 474 | ], 475 | "jobRole": null, 476 | "applyInfo": { 477 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1231\/deputy-head-of-department-creative-professions-and-digital-arts\/?trackid=22", 478 | "type": "external-redirect", 479 | "allowMobileExternalRedirects": true, 480 | "externalApplicationUrl": "https:\/\/jobs.gre.ac.uk\/Vacancy.aspx?ref=840" 481 | }, 482 | "upsells": [ 483 | 8, 484 | 5, 485 | 7, 486 | 6 487 | ], 488 | "noOfVideos": 0, 489 | "isPremiumJob": false, 490 | "isHomePageJob": false, 491 | "isDisplayLogoJob": true, 492 | "isTopJob": false, 493 | "categories": { 494 | "function": [ 495 | [ 496 | "Senior Management & Heads of Department", 497 | [ 498 | "Heads of Department", 499 | "Other Senior Management" 500 | ] 501 | ] 502 | ], 503 | "location": [ 504 | 505 | ] 506 | }, 507 | "urlId": "1231\/deputy-head-of-department-creative-professions-and-digital-arts", 508 | "usesAltRecruiterName": false 509 | }, 510 | { 511 | "id": 1297, 512 | "title": "Quantity Surveyor", 513 | "posted": "16 Apr 2015", 514 | "description": "", 515 | "recruiter": "Transport for London (Madgex Test Data)", 516 | "recruiterID": 21, 517 | "recruiterLogo": "8bc4bed9-0fc3-42b3-a3a0-920e195f8ca4", 518 | "salary": "\u00a3180,001 - \u00a3190,000", 519 | "location": "B34 7JL, Birmingham", 520 | "geo": [ 521 | 522 | ], 523 | "jobRole": null, 524 | "applyInfo": { 525 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1297\/quantity-surveyor\/?trackid=22", 526 | "type": "email-applications", 527 | "allowMobileExternalRedirects": true 528 | }, 529 | "upsells": [ 530 | 531 | ], 532 | "noOfVideos": 0, 533 | "isPremiumJob": false, 534 | "isHomePageJob": false, 535 | "isDisplayLogoJob": false, 536 | "isTopJob": false, 537 | "categories": { 538 | "function": [ 539 | [ 540 | "Academic Related", 541 | [ 542 | "Technicians" 543 | ] 544 | ] 545 | ], 546 | "location": [ 547 | 548 | ] 549 | }, 550 | "urlId": "1297\/quantity-surveyor", 551 | "usesAltRecruiterName": false 552 | }, 553 | { 554 | "id": 1299, 555 | "title": "Strategic Procurement Manager", 556 | "posted": "16 Apr 2015", 557 | "description": "", 558 | "recruiter": "Transport for London (Madgex Test Data)", 559 | "recruiterID": 21, 560 | "recruiterLogo": "8bc4bed9-0fc3-42b3-a3a0-920e195f8ca4", 561 | "salary": "\u00a3120,001 - \u00a3130,000", 562 | "location": "CM77 8NJ, Braintree", 563 | "geo": [ 564 | 565 | ], 566 | "jobRole": null, 567 | "applyInfo": { 568 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1299\/strategic-procurement-manager\/?trackid=22", 569 | "type": "email-applications", 570 | "allowMobileExternalRedirects": true 571 | }, 572 | "upsells": [ 573 | 5 574 | ], 575 | "noOfVideos": 0, 576 | "isPremiumJob": true, 577 | "isHomePageJob": false, 578 | "isDisplayLogoJob": false, 579 | "isTopJob": false, 580 | "categories": { 581 | "function": [ 582 | [ 583 | "Professional Services", 584 | [ 585 | "Human Resources" 586 | ] 587 | ] 588 | ], 589 | "location": [ 590 | 591 | ] 592 | }, 593 | "urlId": "1299\/strategic-procurement-manager", 594 | "usesAltRecruiterName": false 595 | }, 596 | { 597 | "id": 1298, 598 | "title": "Trust Fundraiser", 599 | "posted": "16 Apr 2015", 600 | "description": "", 601 | "recruiter": "Transport for London (Madgex Test Data)", 602 | "recruiterID": 21, 603 | "recruiterLogo": "8bc4bed9-0fc3-42b3-a3a0-920e195f8ca4", 604 | "salary": "Hourly Rate", 605 | "location": "SG1 6AB, Stevenage", 606 | "geo": [ 607 | 608 | ], 609 | "jobRole": null, 610 | "applyInfo": { 611 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1298\/trust-fundraiser\/?trackid=22", 612 | "type": "email-applications", 613 | "allowMobileExternalRedirects": true 614 | }, 615 | "upsells": [ 616 | 617 | ], 618 | "noOfVideos": 0, 619 | "isPremiumJob": false, 620 | "isHomePageJob": false, 621 | "isDisplayLogoJob": false, 622 | "isTopJob": false, 623 | "categories": { 624 | "function": [ 625 | [ 626 | "Senior Management & Heads of Department", 627 | [ 628 | "Vice-chancellors \/ Principals \/ Provosts" 629 | ] 630 | ] 631 | ], 632 | "location": [ 633 | 634 | ] 635 | }, 636 | "urlId": "1298\/trust-fundraiser", 637 | "usesAltRecruiterName": false 638 | }, 639 | { 640 | "id": 1300, 641 | "title": "Business Travel Reservations", 642 | "posted": "16 Apr 2015", 643 | "description": "", 644 | "recruiter": "Transport for London (Madgex Test Data)", 645 | "recruiterID": 21, 646 | "recruiterLogo": "8bc4bed9-0fc3-42b3-a3a0-920e195f8ca4", 647 | "salary": "\u00a3110,001 - \u00a3120,000", 648 | "location": "S65 4DA, Rotherham", 649 | "geo": [ 650 | 651 | ], 652 | "jobRole": null, 653 | "applyInfo": { 654 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1300\/business-travel-reservations\/?trackid=22", 655 | "type": "response-management", 656 | "allowMobileExternalRedirects": true 657 | }, 658 | "upsells": [ 659 | 5, 660 | 6, 661 | 7 662 | ], 663 | "noOfVideos": 0, 664 | "isPremiumJob": true, 665 | "isHomePageJob": false, 666 | "isDisplayLogoJob": false, 667 | "isTopJob": false, 668 | "categories": { 669 | "function": [ 670 | [ 671 | "Senior Management & Heads of Department", 672 | [ 673 | "Other Senior Management" 674 | ] 675 | ], 676 | [ 677 | "Academic Posts", 678 | [ 679 | "Postdocs", 680 | "Studentships" 681 | ] 682 | ] 683 | ], 684 | "location": [ 685 | 686 | ] 687 | }, 688 | "urlId": "1300\/business-travel-reservations", 689 | "usesAltRecruiterName": false 690 | }, 691 | { 692 | "id": 1301, 693 | "title": "Business Travel Reservations", 694 | "posted": "16 Apr 2015", 695 | "description": "", 696 | "recruiter": "Transport for London (Madgex Test Data)", 697 | "recruiterID": 21, 698 | "recruiterLogo": "8bc4bed9-0fc3-42b3-a3a0-920e195f8ca4", 699 | "salary": "\u00a3110,001 - \u00a3120,000", 700 | "location": "S65 4DA, Rotherham", 701 | "geo": [ 702 | 703 | ], 704 | "jobRole": null, 705 | "applyInfo": { 706 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1301\/business-travel-reservations\/?trackid=22", 707 | "type": "response-management", 708 | "allowMobileExternalRedirects": true 709 | }, 710 | "upsells": [ 711 | 712 | ], 713 | "noOfVideos": 0, 714 | "isPremiumJob": false, 715 | "isHomePageJob": false, 716 | "isDisplayLogoJob": false, 717 | "isTopJob": false, 718 | "categories": { 719 | "function": [ 720 | [ 721 | "Senior Management & Heads of Department", 722 | [ 723 | "Other Senior Management" 724 | ] 725 | ], 726 | [ 727 | "Academic Posts", 728 | [ 729 | "Postdocs", 730 | "Studentships" 731 | ] 732 | ] 733 | ], 734 | "location": [ 735 | 736 | ] 737 | }, 738 | "urlId": "1301\/business-travel-reservations", 739 | "usesAltRecruiterName": false 740 | }, 741 | { 742 | "id": 1305, 743 | "title": "Tele-Sales", 744 | "posted": "17 Apr 2015", 745 | "description": "", 746 | "recruiter": "Sky (Madgex Test Data)", 747 | "recruiterID": 19, 748 | "recruiterLogo": "d3031aac-8566-4227-ae58-c72811cc20e6", 749 | "salary": "\u00a370,001 - \u00a380,000", 750 | "location": "LE5 5TH, Leicester", 751 | "geo": [ 752 | 753 | ], 754 | "jobRole": null, 755 | "applyInfo": { 756 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1305\/tele-sales\/?trackid=22", 757 | "type": "email-applications", 758 | "allowMobileExternalRedirects": true 759 | }, 760 | "upsells": [ 761 | 762 | ], 763 | "noOfVideos": 0, 764 | "isPremiumJob": false, 765 | "isHomePageJob": false, 766 | "isDisplayLogoJob": false, 767 | "isTopJob": false, 768 | "categories": { 769 | "function": [ 770 | [ 771 | "Senior Management & Heads of Department", 772 | [ 773 | "Chief Operating Officers", 774 | "Directors", 775 | "Other Senior Management" 776 | ] 777 | ], 778 | [ 779 | "Professional Services", 780 | [ 781 | "Marketing and Communications" 782 | ] 783 | ] 784 | ], 785 | "location": [ 786 | 787 | ] 788 | }, 789 | "urlId": "1305\/tele-sales", 790 | "usesAltRecruiterName": false 791 | }, 792 | { 793 | "id": 1307, 794 | "title": "Health and Safety Officer", 795 | "posted": "17 Apr 2015", 796 | "description": "", 797 | "recruiter": "Sky (Madgex Test Data)", 798 | "recruiterID": 19, 799 | "recruiterLogo": "d3031aac-8566-4227-ae58-c72811cc20e6", 800 | "salary": "\u00a310,001 - \u00a320,000", 801 | "location": "EH12 7UH, Edinburgh", 802 | "geo": [ 803 | 804 | ], 805 | "jobRole": null, 806 | "applyInfo": { 807 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1307\/health-and-safety-officer\/?trackid=22", 808 | "type": "email-applications", 809 | "allowMobileExternalRedirects": true 810 | }, 811 | "upsells": [ 812 | 5, 813 | 6, 814 | 7 815 | ], 816 | "noOfVideos": 0, 817 | "isPremiumJob": true, 818 | "isHomePageJob": false, 819 | "isDisplayLogoJob": false, 820 | "isTopJob": false, 821 | "categories": { 822 | "function": [ 823 | [ 824 | "Academic Related", 825 | [ 826 | "Other Academic Related" 827 | ] 828 | ], 829 | [ 830 | "Professional Services", 831 | [ 832 | "Knowledge Transfer \/ Business Links", 833 | "Library \/ Records \/ Information Management" 834 | ] 835 | ] 836 | ], 837 | "location": [ 838 | 839 | ] 840 | }, 841 | "urlId": "1307\/health-and-safety-officer", 842 | "usesAltRecruiterName": false 843 | }, 844 | { 845 | "id": 1306, 846 | "title": "Legal Adviser", 847 | "posted": "17 Apr 2015", 848 | "description": "", 849 | "recruiter": "Sky (Madgex Test Data)", 850 | "recruiterID": 19, 851 | "recruiterLogo": "d3031aac-8566-4227-ae58-c72811cc20e6", 852 | "salary": "Daily Rate", 853 | "location": "TR18 2FE, Penzance", 854 | "geo": [ 855 | 856 | ], 857 | "jobRole": null, 858 | "applyInfo": { 859 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1306\/legal-adviser\/?trackid=22", 860 | "type": "email-applications", 861 | "allowMobileExternalRedirects": true 862 | }, 863 | "upsells": [ 864 | 5, 865 | 6 866 | ], 867 | "noOfVideos": 0, 868 | "isPremiumJob": true, 869 | "isHomePageJob": false, 870 | "isDisplayLogoJob": false, 871 | "isTopJob": false, 872 | "categories": { 873 | "function": [ 874 | [ 875 | "Senior Management & Heads of Department", 876 | [ 877 | "Pro Vice-chancellors" 878 | ] 879 | ], 880 | [ 881 | "Academic Posts", 882 | [ 883 | "Readers \/ Senior Research Fellows" 884 | ] 885 | ], 886 | "Academic Related", 887 | [ 888 | "Professional Services", 889 | [ 890 | "Administrative", 891 | "Fundraising, Development and Alumni" 892 | ] 893 | ] 894 | ], 895 | "location": [ 896 | 897 | ] 898 | }, 899 | "urlId": "1306\/legal-adviser", 900 | "usesAltRecruiterName": false 901 | }, 902 | { 903 | "id": 1310, 904 | "title": "Plant Manager", 905 | "posted": "20 Apr 2015", 906 | "description": "", 907 | "recruiter": "Marriott Hotels (Madgex Test Data)", 908 | "recruiterID": 15, 909 | "recruiterLogo": "d26e36da-6874-4f36-a183-5a34b1463424", 910 | "salary": "\u00a3130,001 - \u00a3140,000", 911 | "location": "IP28 8AT, Bury St Edmunds", 912 | "geo": [ 913 | 914 | ], 915 | "jobRole": null, 916 | "applyInfo": { 917 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1310\/plant-manager\/?trackid=22", 918 | "type": "email-applications", 919 | "allowMobileExternalRedirects": true 920 | }, 921 | "upsells": [ 922 | 923 | ], 924 | "noOfVideos": 0, 925 | "isPremiumJob": false, 926 | "isHomePageJob": false, 927 | "isDisplayLogoJob": false, 928 | "isTopJob": false, 929 | "categories": { 930 | "function": [ 931 | [ 932 | "Senior Management & Heads of Department", 933 | [ 934 | "Directors" 935 | ] 936 | ], 937 | [ 938 | "Academic Posts", 939 | [ 940 | "Studentships" 941 | ] 942 | ], 943 | [ 944 | "Professional Services", 945 | [ 946 | "Finance and Procurement" 947 | ] 948 | ] 949 | ], 950 | "location": [ 951 | 952 | ] 953 | }, 954 | "urlId": "1310\/plant-manager", 955 | "usesAltRecruiterName": false 956 | }, 957 | { 958 | "id": 1311, 959 | "title": "Legal Adviser", 960 | "posted": "20 Apr 2015", 961 | "description": "", 962 | "recruiter": "Sky (Madgex Test Data)", 963 | "recruiterID": 19, 964 | "recruiterLogo": "d3031aac-8566-4227-ae58-c72811cc20e6", 965 | "salary": "Daily Rate", 966 | "location": "TR18 2FE, Penzance", 967 | "geo": [ 968 | 969 | ], 970 | "jobRole": null, 971 | "applyInfo": { 972 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1311\/legal-adviser\/?trackid=22", 973 | "type": "email-applications", 974 | "allowMobileExternalRedirects": true 975 | }, 976 | "upsells": [ 977 | 978 | ], 979 | "noOfVideos": 0, 980 | "isPremiumJob": false, 981 | "isHomePageJob": false, 982 | "isDisplayLogoJob": false, 983 | "isTopJob": false, 984 | "categories": { 985 | "function": [ 986 | [ 987 | "Senior Management & Heads of Department", 988 | [ 989 | "Pro Vice-chancellors" 990 | ] 991 | ], 992 | [ 993 | "Academic Posts", 994 | [ 995 | "Readers \/ Senior Research Fellows" 996 | ] 997 | ], 998 | "Academic Related", 999 | [ 1000 | "Professional Services", 1001 | [ 1002 | "Administrative", 1003 | "Fundraising, Development and Alumni" 1004 | ] 1005 | ] 1006 | ], 1007 | "location": [ 1008 | 1009 | ] 1010 | }, 1011 | "urlId": "1311\/legal-adviser", 1012 | "usesAltRecruiterName": false 1013 | }, 1014 | { 1015 | "id": 1324, 1016 | "title": "Vehicle Technician", 1017 | "posted": "20 Apr 2015", 1018 | "description": "", 1019 | "recruiter": "Sky (Madgex Test Data)", 1020 | "recruiterID": 19, 1021 | "recruiterLogo": "d3031aac-8566-4227-ae58-c72811cc20e6", 1022 | "salary": "Daily Rate", 1023 | "location": "HU3 1XR, Kingston upon Hull", 1024 | "geo": [ 1025 | 1026 | ], 1027 | "jobRole": null, 1028 | "applyInfo": { 1029 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1324\/vehicle-technician\/?trackid=22", 1030 | "type": "email-applications", 1031 | "allowMobileExternalRedirects": true 1032 | }, 1033 | "upsells": [ 1034 | 1035 | ], 1036 | "noOfVideos": 0, 1037 | "isPremiumJob": false, 1038 | "isHomePageJob": false, 1039 | "isDisplayLogoJob": false, 1040 | "isTopJob": false, 1041 | "categories": { 1042 | "function": [ 1043 | [ 1044 | "Senior Management & Heads of Department", 1045 | [ 1046 | "Deans" 1047 | ] 1048 | ], 1049 | [ 1050 | "Academic Posts", 1051 | [ 1052 | "Lecturers \/ Assistant Professors", 1053 | "Postdocs" 1054 | ] 1055 | ], 1056 | [ 1057 | "Professional Services", 1058 | [ 1059 | "Registry" 1060 | ] 1061 | ] 1062 | ], 1063 | "location": [ 1064 | 1065 | ] 1066 | }, 1067 | "urlId": "1324\/vehicle-technician", 1068 | "usesAltRecruiterName": false 1069 | }, 1070 | { 1071 | "id": 1325, 1072 | "title": "Project Manager", 1073 | "posted": "20 Apr 2015", 1074 | "description": "", 1075 | "recruiter": "Sky (Madgex Test Data)", 1076 | "recruiterID": 19, 1077 | "recruiterLogo": "d3031aac-8566-4227-ae58-c72811cc20e6", 1078 | "salary": "\u00a330,001 - \u00a340,000", 1079 | "location": "WS3 2QE, Walsall", 1080 | "geo": [ 1081 | 1082 | ], 1083 | "jobRole": null, 1084 | "applyInfo": { 1085 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1325\/project-manager\/?trackid=22", 1086 | "type": "offline-applications", 1087 | "allowMobileExternalRedirects": true 1088 | }, 1089 | "upsells": [ 1090 | 1091 | ], 1092 | "noOfVideos": 0, 1093 | "isPremiumJob": false, 1094 | "isHomePageJob": false, 1095 | "isDisplayLogoJob": false, 1096 | "isTopJob": false, 1097 | "categories": { 1098 | "function": [ 1099 | [ 1100 | "Professional Services", 1101 | [ 1102 | "Student Services" 1103 | ] 1104 | ] 1105 | ], 1106 | "location": [ 1107 | 1108 | ] 1109 | }, 1110 | "urlId": "1325\/project-manager", 1111 | "usesAltRecruiterName": false 1112 | }, 1113 | { 1114 | "id": 1230, 1115 | "title": "Deputy Vice-Chancellor (Research and Enterprise)", 1116 | "posted": "21 Apr 2015", 1117 | "description": "", 1118 | "recruiter": "University of Greenwich", 1119 | "recruiterID": 1229, 1120 | "recruiterLogo": "69beea65-72a0-4dde-b5e2-0122b9e4532c", 1121 | "salary": "Attractive six-figure salary", 1122 | "location": "London (Greater)", 1123 | "geo": [ 1124 | 1125 | ], 1126 | "jobRole": null, 1127 | "applyInfo": { 1128 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1230\/deputy-vice-chancellor-research-and-enterprise-\/?trackid=22", 1129 | "type": "external-redirect", 1130 | "allowMobileExternalRedirects": true, 1131 | "externalApplicationUrl": "http:\/\/www.odgers.com\/51759" 1132 | }, 1133 | "upsells": [ 1134 | 8, 1135 | 5, 1136 | 7, 1137 | 6, 1138 | 7 1139 | ], 1140 | "noOfVideos": 0, 1141 | "isPremiumJob": false, 1142 | "isHomePageJob": true, 1143 | "isDisplayLogoJob": true, 1144 | "isTopJob": false, 1145 | "categories": { 1146 | "function": [ 1147 | [ 1148 | "Senior Management & Heads of Department", 1149 | [ 1150 | "Deputy Vice-chancellors" 1151 | ] 1152 | ] 1153 | ], 1154 | "location": [ 1155 | 1156 | ] 1157 | }, 1158 | "urlId": "1230\/deputy-vice-chancellor-research-and-enterprise-", 1159 | "usesAltRecruiterName": false 1160 | }, 1161 | { 1162 | "id": 1228, 1163 | "title": "Research Lecturer\/Senior Lecturer in Artificial Intelligence\/Interactive Systems", 1164 | "posted": "21 Apr 2015", 1165 | "description": "", 1166 | "recruiter": "Teesside University", 1167 | "recruiterID": 1215, 1168 | "recruiterLogo": "cb7c6707-bb9e-464b-b168-e166d4f1e3d5", 1169 | "salary": "\u00a329,552 - \u00a347,328", 1170 | "location": "Middlesbrough, North Yorkshire", 1171 | "geo": [ 1172 | 1173 | ], 1174 | "jobRole": null, 1175 | "applyInfo": { 1176 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1228\/research-lecturer-senior-lecturer-in-artificial-intelligence-interactive-systems\/?trackid=22", 1177 | "type": "external-redirect", 1178 | "allowMobileExternalRedirects": true, 1179 | "externalApplicationUrl": "http:\/\/www.tees.ac.uk\/sections\/jobs" 1180 | }, 1181 | "upsells": [ 1182 | 6 1183 | ], 1184 | "noOfVideos": 0, 1185 | "isPremiumJob": false, 1186 | "isHomePageJob": false, 1187 | "isDisplayLogoJob": true, 1188 | "isTopJob": false, 1189 | "categories": { 1190 | "function": [ 1191 | [ 1192 | "Academic Posts", 1193 | [ 1194 | "Principal \/ Senior Lecturers \/ Associate Professors", 1195 | "Lecturers \/ Assistant Professors" 1196 | ] 1197 | ] 1198 | ], 1199 | "location": [ 1200 | 1201 | ] 1202 | }, 1203 | "urlId": "1228\/research-lecturer-senior-lecturer-in-artificial-intelligence-interactive-systems", 1204 | "usesAltRecruiterName": false 1205 | }, 1206 | { 1207 | "id": 1227, 1208 | "title": "Research Lecturer\/Senior Lecturer in Criminology", 1209 | "posted": "21 Apr 2015", 1210 | "description": "", 1211 | "recruiter": "Teesside University", 1212 | "recruiterID": 1215, 1213 | "recruiterLogo": "cb7c6707-bb9e-464b-b168-e166d4f1e3d5", 1214 | "salary": "\u00a329,552 - \u00a347,328", 1215 | "location": "Middlesbrough, North Yorkshire", 1216 | "geo": [ 1217 | 1218 | ], 1219 | "jobRole": null, 1220 | "applyInfo": { 1221 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1227\/research-lecturer-senior-lecturer-in-criminology\/?trackid=22", 1222 | "type": "external-redirect", 1223 | "allowMobileExternalRedirects": true, 1224 | "externalApplicationUrl": "http:\/\/www.tees.ac.uk\/sections\/jobs" 1225 | }, 1226 | "upsells": [ 1227 | 6 1228 | ], 1229 | "noOfVideos": 0, 1230 | "isPremiumJob": false, 1231 | "isHomePageJob": false, 1232 | "isDisplayLogoJob": true, 1233 | "isTopJob": false, 1234 | "categories": { 1235 | "function": [ 1236 | [ 1237 | "Academic Posts", 1238 | [ 1239 | "Principal \/ Senior Lecturers \/ Associate Professors", 1240 | "Lecturers \/ Assistant Professors" 1241 | ] 1242 | ] 1243 | ], 1244 | "location": [ 1245 | 1246 | ] 1247 | }, 1248 | "urlId": "1227\/research-lecturer-senior-lecturer-in-criminology", 1249 | "usesAltRecruiterName": false 1250 | }, 1251 | { 1252 | "id": 1226, 1253 | "title": "Research Lecturer\/Senior Lecturer in Business and Management", 1254 | "posted": "21 Apr 2015", 1255 | "description": "", 1256 | "recruiter": "Teesside University", 1257 | "recruiterID": 1215, 1258 | "recruiterLogo": "cb7c6707-bb9e-464b-b168-e166d4f1e3d5", 1259 | "salary": "\u00a329,552 - \u00a347,328", 1260 | "location": "Middlesbrough, North Yorkshire", 1261 | "geo": [ 1262 | 1263 | ], 1264 | "jobRole": null, 1265 | "applyInfo": { 1266 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1226\/research-lecturer-senior-lecturer-in-business-and-management\/?trackid=22", 1267 | "type": "external-redirect", 1268 | "allowMobileExternalRedirects": true, 1269 | "externalApplicationUrl": "http:\/\/www.tees.ac.uk\/sections\/jobs" 1270 | }, 1271 | "upsells": [ 1272 | 6 1273 | ], 1274 | "noOfVideos": 0, 1275 | "isPremiumJob": false, 1276 | "isHomePageJob": false, 1277 | "isDisplayLogoJob": true, 1278 | "isTopJob": false, 1279 | "categories": { 1280 | "function": [ 1281 | [ 1282 | "Academic Posts", 1283 | [ 1284 | "Principal \/ Senior Lecturers \/ Associate Professors", 1285 | "Lecturers \/ Assistant Professors" 1286 | ] 1287 | ] 1288 | ], 1289 | "location": [ 1290 | 1291 | ] 1292 | }, 1293 | "urlId": "1226\/research-lecturer-senior-lecturer-in-business-and-management", 1294 | "usesAltRecruiterName": false 1295 | }, 1296 | { 1297 | "id": 1225, 1298 | "title": "Research Lecturer\/Senior Lecturer in Social Policy\/Sociology", 1299 | "posted": "21 Apr 2015", 1300 | "description": "", 1301 | "recruiter": "Teesside University", 1302 | "recruiterID": 1215, 1303 | "recruiterLogo": "cb7c6707-bb9e-464b-b168-e166d4f1e3d5", 1304 | "salary": "\u00a329,552 - \u00a347,328", 1305 | "location": "Middlesbrough, North Yorkshire", 1306 | "geo": [ 1307 | 1308 | ], 1309 | "jobRole": null, 1310 | "applyInfo": { 1311 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1225\/research-lecturer-senior-lecturer-in-social-policy-sociology\/?trackid=22", 1312 | "type": "external-redirect", 1313 | "allowMobileExternalRedirects": true, 1314 | "externalApplicationUrl": "http:\/\/www.tees.ac.uk\/sections\/jobs" 1315 | }, 1316 | "upsells": [ 1317 | 6 1318 | ], 1319 | "noOfVideos": 0, 1320 | "isPremiumJob": false, 1321 | "isHomePageJob": false, 1322 | "isDisplayLogoJob": true, 1323 | "isTopJob": false, 1324 | "categories": { 1325 | "function": [ 1326 | [ 1327 | "Academic Posts", 1328 | [ 1329 | "Principal \/ Senior Lecturers \/ Associate Professors", 1330 | "Lecturers \/ Assistant Professors" 1331 | ] 1332 | ] 1333 | ], 1334 | "location": [ 1335 | 1336 | ] 1337 | }, 1338 | "urlId": "1225\/research-lecturer-senior-lecturer-in-social-policy-sociology", 1339 | "usesAltRecruiterName": false 1340 | }, 1341 | { 1342 | "id": 1224, 1343 | "title": "Research Lecturer\/Senior Lecturer in Digital Media & Culture", 1344 | "posted": "21 Apr 2015", 1345 | "description": "", 1346 | "recruiter": "Teesside University", 1347 | "recruiterID": 1215, 1348 | "recruiterLogo": "cb7c6707-bb9e-464b-b168-e166d4f1e3d5", 1349 | "salary": "\u00a329,552 - \u00a347,328", 1350 | "location": "Middlesbrough, North Yorkshire", 1351 | "geo": [ 1352 | 1353 | ], 1354 | "jobRole": null, 1355 | "applyInfo": { 1356 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1224\/research-lecturer-senior-lecturer-in-digital-media-and-culture\/?trackid=22", 1357 | "type": "external-redirect", 1358 | "allowMobileExternalRedirects": true, 1359 | "externalApplicationUrl": "http:\/\/www.tees.ac.uk\/sections\/jobs" 1360 | }, 1361 | "upsells": [ 1362 | 6 1363 | ], 1364 | "noOfVideos": 0, 1365 | "isPremiumJob": false, 1366 | "isHomePageJob": false, 1367 | "isDisplayLogoJob": true, 1368 | "isTopJob": false, 1369 | "categories": { 1370 | "function": [ 1371 | [ 1372 | "Academic Posts", 1373 | [ 1374 | "Principal \/ Senior Lecturers \/ Associate Professors", 1375 | "Lecturers \/ Assistant Professors" 1376 | ] 1377 | ] 1378 | ], 1379 | "location": [ 1380 | 1381 | ] 1382 | }, 1383 | "urlId": "1224\/research-lecturer-senior-lecturer-in-digital-media-and-culture", 1384 | "usesAltRecruiterName": false 1385 | }, 1386 | { 1387 | "id": 1223, 1388 | "title": "Research Lecturer\/Senior Lecturer in Health Sciences Research (Systematic Reviewing)", 1389 | "posted": "21 Apr 2015", 1390 | "description": "", 1391 | "recruiter": "Teesside University", 1392 | "recruiterID": 1215, 1393 | "recruiterLogo": "cb7c6707-bb9e-464b-b168-e166d4f1e3d5", 1394 | "salary": "\u00a329,552 - \u00a347,328", 1395 | "location": "Middlesbrough, North Yorkshire", 1396 | "geo": [ 1397 | 1398 | ], 1399 | "jobRole": null, 1400 | "applyInfo": { 1401 | "applyUrl": "http:\/\/guidesmiths-web.madgexjbtest.com\/apply\/1223\/research-lecturer-senior-lecturer-in-health-sciences-research-systematic-reviewing-\/?trackid=22", 1402 | "type": "external-redirect", 1403 | "allowMobileExternalRedirects": true, 1404 | "externalApplicationUrl": "http:\/\/www.tees.ac.uk\/sections\/jobs" 1405 | }, 1406 | "upsells": [ 1407 | 6 1408 | ], 1409 | "noOfVideos": 0, 1410 | "isPremiumJob": false, 1411 | "isHomePageJob": false, 1412 | "isDisplayLogoJob": true, 1413 | "isTopJob": false, 1414 | "categories": { 1415 | "function": [ 1416 | [ 1417 | "Academic Posts", 1418 | [ 1419 | "Principal \/ Senior Lecturers \/ Associate Professors", 1420 | "Lecturers \/ Assistant Professors" 1421 | ] 1422 | ] 1423 | ], 1424 | "location": [ 1425 | 1426 | ] 1427 | }, 1428 | "urlId": "1223\/research-lecturer-senior-lecturer-in-health-sciences-research-systematic-reviewing-", 1429 | "usesAltRecruiterName": false 1430 | } 1431 | ] 1432 | } 1433 | } --------------------------------------------------------------------------------