Compare commits

...

43 Commits

Author SHA1 Message Date
Diana Lau a529986c65
Merge pull request #106 from strongloop/docs/end-of-life
docs: end of life
2021-03-05 19:59:17 -05:00
Miroslav Bajtoš c362bcf930
docs: end of life
LB3 has reached end of life and this connector does not support LB4.

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2021-03-03 09:23:27 +01:00
Miroslav Bajtoš 64c88ab382
Merge pull request #105 from strongloop/node14
chore: add Node.js 14 to CI
2020-09-11 11:58:46 +02:00
Diana Lau 4b5ac98379 chore: add Node.js 14 to CI
Signed-off-by: Diana Lau <dhmlau@ca.ibm.com>
2020-09-10 15:15:24 -04:00
Diana Lau 2120e96dbf
Merge pull request #104 from strongloop/dco
chore: switch to DCO
2020-08-31 13:25:21 -04:00
Diana Lau be758fa770 chore: switch to DCO
Signed-off-by: Diana Lau <dhmlau@ca.ibm.com>
2020-08-20 21:22:09 -04:00
Agnes Lin b8fd18e217
Merge pull request #103 from strongloop/copyright
chore: update copyright year
2020-02-10 14:05:41 -05:00
Diana Lau 334b705c69 chore: update copyright year 2020-02-10 13:49:27 -05:00
Nora 4bfa461229
Merge pull request #101 from strongloop/chore/improve-issue-templates
chore: improve issue and PR templates
2019-11-21 09:46:27 -05:00
Nora 4f8a372d2c chore: improve issue and PR templates 2019-11-19 11:25:06 -05:00
Nora 22ee132941
Merge pull request #102 from strongloop/fix-comma-dangle
chore: fix eslint violations
2019-11-19 11:24:39 -05:00
Nora 1975481cb0 chore: fix eslint violations
Fix comma-dangle violations
2019-11-15 21:03:12 -05:00
Miroslav Bajtoš c03161dfe0
Merge pull request #100 from strongloop/fix/loopback-remote-invoke-updateAll
Add a test to verify PersistedModel updateAll
2019-10-07 11:12:09 +02:00
Miroslav Bajtoš 39771be12f
Add a test to verify PersistedModel updateAll
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-07 10:49:57 +02:00
Miroslav Bajtoš 4ff55feb24
Merge pull request #99 from strongloop/update-eslint
Update eslint & config to latest
2019-10-04 10:04:32 +02:00
Miroslav Bajtoš 0097580537
Update eslint & config to latest
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-03 10:54:13 +02:00
Miroslav Bajtoš 4efca1f052
Merge pull request #95 from angfal/fix/remote_exists
Add tests to check Model.exists() working
2019-08-23 10:27:56 +02:00
Maxim Sharai 2aff46a79a
Add tests to verify Model.exists() is working 2019-08-23 10:16:02 +02:00
Nora 6caf45d39e
Merge pull request #97 from strongloop/add-node-12
Drop support for Node.js 6 and add Node.js 12 to CI
2019-07-25 17:46:11 -04:00
Nora acbe35879b add node 12 to CI 2019-07-25 15:39:50 -04:00
Nora e5e4ba93ef drop support for node 6 2019-07-25 15:39:33 -04:00
Diana Lau 61f031aeb9
Merge pull request #96 from strongloop/copyrights
chore: update copyrights years
2019-05-08 11:47:25 -04:00
Agnes Lin 59f70ab3ce chore: update copyrights years 2019-05-07 13:34:15 -04:00
Miroslav Bajtoš 3e7537e1dd
3.4.1
* fix: return null when findById/findOne returns 404 (Jannis Ötjengerdes)
 * style: fix linting (virkt25)
2019-01-22 16:47:50 +01:00
Miroslav Bajtoš 4245df5c41
Merge pull request #94 from angfal/404_error_catch
Catch error and return null when findById/findOne doesn't find an entity
2019-01-22 16:46:58 +01:00
Jannis Ötjengerdes 4108db8945
fix: return null when findById/findOne returns 404
Co-authored-by: Maxim Sharai <maxim.sharai@tispr.com>
Co-authored-by: Jannis Ötjengerdes <joetjengerdes@rightmart.de>
2019-01-21 16:32:41 +01:00
virkt25 1a7b1be96d style: fix linting
- update eslint and eslint config
- fix linting
2018-08-24 09:14:41 -04:00
Taranveer Virk 6e09d0b4bf 3.4.0
* move dependencies (Stephen Crawford)
 * chore: drop node 4 and update deps (Taranveer Virk)
2018-06-21 02:13:08 -04:00
Taranveer Virk ae64f198ae
Merge pull request #89 from srcrawford/master
Move eslint dependencies to devDependencies
2018-06-21 02:10:10 -04:00
Stephen Crawford a504e25752 move dependencies 2018-06-21 00:46:37 +01:00
Taranveer Virk e2dad4e39a
Merge pull request #88 from strongloop/drop-node
chore: drop node 4 and update deps
2018-06-20 13:33:36 -04:00
Taranveer Virk 974694b526 chore: drop node 4 and update deps
Signed-off-by: Taranveer Virk <taranveer@virk.cc>
2018-06-20 10:29:51 -04:00
Miroslav Bajtoš 0c00806f77
3.3.1
* chore: update copyright notice years (Miroslav Bajtoš)
 * Fix duplicate definition of a remote model type (maxim.sharai)
2018-01-16 15:55:45 +01:00
Miroslav Bajtoš 60c081f893
chore: update copyright notice years 2018-01-16 15:55:09 +01:00
Miroslav Bajtoš 823a20f89d
Merge pull request #85 from angfal/#81
Fix duplicate definition of a remote model type
2018-01-16 15:52:48 +01:00
maxim.sharai af125c50cc
Fix duplicate definition of a remote model type
Before this commit, when a remote model had relations, the model was
registered an additional time per each relation. As a result,
the following warnings were printed to the console

   Warning: overriding remoting type $MODEL_NAME

This commit fixes registration of models with strong-remoting to avoid
those warnings.
2018-01-16 15:15:45 +01:00
Miroslav Bajtoš 906979a960
3.3.0
* Gruntfile: remove forgotten jshint task (Miroslav Bajtoš)
 * Preserve related models from "include" filter (Dimitris)
 * Add eslint to npm test, fix linter issues (Miroslav Bajtoš)
 * Refactor tests to use local per-app model registry (Miroslav Bajtoš)
2017-12-12 17:26:04 +01:00
Miroslav Bajtoš d622ce5ed8
Merge pull request #78 from mitsos1os/issue-42
Support retrieved related models from remote loopback service
2017-12-12 13:08:55 +01:00
Miroslav Bajtoš 4ec0ec5341
Gruntfile: remove forgotten jshint task 2017-12-12 09:39:46 +01:00
Dimitris a3d110b78c
Preserve related models from "include" filter
Before this change, when making a remote call with "include" filter
(for example `findById(11, {include:['children']})`), the related
models were removed from the result.

This commit fixes the implementation to correctly preserve related
models and also to cast them to correct model instances.
2017-12-12 09:39:37 +01:00
Miroslav Bajtoš ffaaec8973
Merge pull request #82 from strongloop/code-cleanup
Code cleanup
2017-12-11 15:43:40 +01:00
Miroslav Bajtoš 55cb88f727
Add eslint to npm test, fix linter issues 2017-12-11 14:51:48 +01:00
Miroslav Bajtoš d6a5c768fd
Refactor tests to use local per-app model registry
Refactor tests to stop sharing global models between different test
suites and use local per-app model registry instead.

Also clean up all test code to use `const` and `let` instead of `var`.
2017-12-11 14:45:43 +01:00
25 changed files with 904 additions and 700 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
coverage

133
.eslintrc
View File

@ -1,134 +1,3 @@
{
"env": {
"browser": false,
"node": true,
"amd": true
},
"rules": {
"no-alert": 2,
"no-array-constructor": 2,
"no-bitwise": 2,
"no-caller": 2,
"no-catch-shadow": 2,
"no-comma-dangle": 2,
"no-cond-assign": 2,
"no-console": 1,
"no-constant-condition": 2,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 1,
"no-dupe-keys": 2,
"no-else-return": 2,
"no-empty": 2,
"no-empty-class": 2,
"no-empty-label": 2,
"no-eq-null": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": 1,
"no-extra-semi": 2,
"no-extra-strict": 2,
"no-fallthrough": 2,
"no-floating-decimal": 1,
"no-func-assign": 2,
"no-global-strict": 2,
"no-implied-eval": 2,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 2,
"no-loop-func": 2,
"no-mixed-requires": [2, false],
"no-multi-str": 2,
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-nested-ternary": 1,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-require": 1,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-path-concat": 0,
"no-plusplus": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-restricted-modules": 0,
"no-return-assign": 2,
"no-script-url": 2,
"no-self-compare": 0,
"no-sequences": 2,
"no-shadow": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-space-before-semi": 2,
"no-sparse-arrays": 2,
"no-sync": 0,
"no-ternary": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undefined": 1,
"no-undef-init": 2,
"no-underscore-dangle": 0,
"no-unreachable": 2,
"no-unused-expressions": 2,
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
"no-use-before-define": 2,
"no-warning-comments": [1, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
"no-with": 2,
"no-wrap-func": 2,
"no-mixed-spaces-and-tabs": [2, false],
"block-scoped-var": 1,
"brace-style": [2, "1tbs"],
"camelcase": 2,
"complexity": [0, 11],
"consistent-return": 2,
"consistent-this": [2, "that"],
"curly": [2, "all"],
"default-case": 1,
"dot-notation": 2,
"eol-last": 2,
"eqeqeq": 2,
"func-names": 0,
"func-style": [2, "declaration"],
"guard-for-in": 1,
"max-depth": [0, 4],
"max-len": [1, 80, 4],
"max-nested-callbacks": [1, 2],
"max-params": [0, 3],
"max-statements": [0, 10],
"handle-callback-err": 1,
"new-cap": 2,
"new-parens": 2,
"one-var": 0,
"quote-props": 1,
"quotes": [2, "single"],
"radix": 2,
"semi": 2,
"sort-vars": 1,
"space-after-keywords": [2, "always"],
"space-in-brackets": [1, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-word-ops": 1,
"strict": 0,
"use-isnan": 2,
"valid-jsdoc": 2,
"valid-typeof": 2,
"wrap-iife": 1,
"wrap-regex": 1,
"yoda": [2, "never"]
}
"extends": "loopback"
}

View File

@ -1,37 +0,0 @@
<!--
Questions:
https://groups.google.com/forum/#!forum/loopbackjs
https://gitter.im/strongloop/loopback
Immediate support:
https://strongloop.com/api-connect-faqs/
https://strongloop.com/node-js/subscription-plans/
-->
# Description/Steps to reproduce
<!--
If feature: A description of the feature
If bug: Steps to reproduce
-->
# Link to reproduction sandbox
<!--
Link to an app sandbox for reproduction
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
-->
# Expected result
<!--
Also include actual results if bug
-->
# Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->

50
.github/ISSUE_TEMPLATE/Bug_report.md vendored Normal file
View File

@ -0,0 +1,50 @@
---
name: Bug report
about: Create a report to help us improve
labels: bug
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
HELP US HELP YOU, PLEASE
- Do a quick search to avoid duplicate issues
- Provide as much information as possible (reproduction sandbox, use case for features, etc.)
- Consider using a more suitable venue for questions such as Stack Overflow, Gitter, etc.
Please fill in the *entire* template below.
-->
## Steps to reproduce
<!-- Describe how to reproduce the issue -->
## Current Behavior
<!-- Describe the observed result -->
## Expected Behavior
<!-- Describe what did you expect instead, what is the desired outcome? -->
## Link to reproduction sandbox
<!--
See https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-3x-bugs
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
-->
## Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->
## Related Issues
<!-- Did you find other bugs that looked similar? -->
_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_

View File

@ -0,0 +1,25 @@
---
name: Feature request
about: Suggest an idea for this project
labels: feature
---
## Suggestion
<!-- A summary of what you'd like to see added or changed -->
## Use Cases
<!--
What do you want to use this for?
What shortcomings exist with current approaches?
-->
## Examples
<!-- Show how this would be used and what the behavior would be -->
## Acceptance criteria
TBD - will be filled by the team.

27
.github/ISSUE_TEMPLATE/Question.md vendored Normal file
View File

@ -0,0 +1,27 @@
---
name: Question
about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help.
labels: question
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
THE ISSUE TRACKER IS NOT FOR QUESTIONS.
DO NOT CREATE A NEW ISSUE TO ASK A QUESTION.
Please use one of the following resources for help:
**Questions**
- https://stackoverflow.com/tags/loopbackjs
- https://groups.google.com/forum/#!forum/loopbackjs
- https://gitter.im/strongloop/loopback
**Immediate support**
- https://strongloop.com/api-connect-faqs/
- https://strongloop.com/node-js/subscription-plans/
-->

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues
about: Do not report security vulnerabilities using GitHub issues. Please send an email to `reachsl@us.ibm.com` instead.
- name: Get help on StackOverflow
url: https://stackoverflow.com/tags/loopbackjs
about: Please ask and answer questions on StackOverflow.
- name: Join our mailing list
url: https://groups.google.com/forum/#!forum/loopbackjs
about: You can also post your question to our mailing list.

View File

@ -1,25 +1,17 @@
### Description
#### Related issues
<!--
Please use the following link syntaxes:
Please provide a high-level description of the changes made by your pull request.
- connect to #49 (to reference issues in the current repository)
- connect to strongloop/loopback#49 (to reference issues in another repository)
Include references to all related GitHub issues and other pull requests, for example:
Fixes #123
Implements #254
See also #23
-->
- connect to <link_to_referenced_issue>
### Checklist
<!--
- Please mark your choice with an "x" (i.e. [x], see
https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments)
- PR's without test coverage will be closed.
-->
## Checklist
- [ ] DCO (Developer Certificate of Origin) [signed in all commits](https://loopback.io/doc/en/contrib/code-contrib.html)
- [ ] `npm test` passes on your machine
- [ ] New tests added or existing tests modified to cover all changes
- [ ] Code conforms with the [style
guide](http://loopback.io/doc/en/contrib/style-guide.html)
- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html)
- [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html)

1
.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

View File

@ -1,6 +1,7 @@
sudo: false
language: node_js
node_js:
- "4"
- "6"
- "8"
- "10"
- "12"
- "14"

View File

@ -1,3 +1,39 @@
2019-01-22, Version 3.4.1
=========================
* fix: return null when findById/findOne returns 404 (Jannis Ötjengerdes)
* style: fix linting (virkt25)
2018-06-21, Version 3.4.0
=========================
* move dependencies (Stephen Crawford)
* chore: drop node 4 and update deps (Taranveer Virk)
2018-01-16, Version 3.3.1
=========================
* chore: update copyright notice years (Miroslav Bajtoš)
* Fix duplicate definition of a remote model type (maxim.sharai)
2017-12-12, Version 3.3.0
=========================
* Gruntfile: remove forgotten jshint task (Miroslav Bajtoš)
* Preserve related models from "include" filter (Dimitris)
* Add eslint to npm test, fix linter issues (Miroslav Bajtoš)
* Refactor tests to use local per-app model registry (Miroslav Bajtoš)
2017-12-05, Version 3.2.0
=========================

View File

@ -19,133 +19,20 @@ Contributing to `loopback-connector-remote` is easy. In a few simple steps:
* Submit a pull request through Github.
### Contributor License Agreement ###
### Developer Certificate of Origin
This project uses [DCO](https://developercertificate.org/). Be sure to sign off
your commits using the `-s` flag or adding `Signed-off-By: Name<Email>` in the
commit message.
**Example**
```
Individual Contributor License Agreement
By signing this Individual Contributor License Agreement
("Agreement"), and making a Contribution (as defined below) to
StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and
agree to the following terms and conditions for Your present and
future Contributions submitted to StrongLoop. Except for the license
granted in this Agreement to StrongLoop and recipients of software
distributed by StrongLoop, You reserve all right, title, and interest
in and to Your Contributions.
1. Definitions
"You" or "Your" shall mean the copyright owner or the individual
authorized by the copyright owner that is entering into this
Agreement with StrongLoop.
"Contribution" shall mean any original work of authorship,
including any modifications or additions to an existing work, that
is intentionally submitted by You to StrongLoop for inclusion in,
or documentation of, any of the products owned or managed by
StrongLoop ("Work"). For purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication
sent to StrongLoop or its representatives, including but not
limited to communication or electronic mailing lists, source code
control systems, and issue tracking systems that are managed by,
or on behalf of, StrongLoop for the purpose of discussing and
improving the Work, but excluding communication that is
conspicuously marked or otherwise designated in writing by You as
"Not a Contribution."
2. You Grant a Copyright License to StrongLoop
Subject to the terms and conditions of this Agreement, You hereby
grant to StrongLoop and recipients of software distributed by
StrongLoop, a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable copyright license to reproduce, prepare
derivative works of, publicly display, publicly perform,
sublicense, and distribute Your Contributions and such derivative
works under any license and without any restrictions.
3. You Grant a Patent License to StrongLoop
Subject to the terms and conditions of this Agreement, You hereby
grant to StrongLoop and to recipients of software distributed by
StrongLoop a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable (except as stated in this Section)
patent license to make, have made, use, offer to sell, sell,
import, and otherwise transfer the Work under any license and
without any restrictions. The patent license You grant to
StrongLoop under this Section applies only to those patent claims
licensable by You that are necessarily infringed by Your
Contributions(s) alone or by combination of Your Contributions(s)
with the Work to which such Contribution(s) was submitted. If any
entity institutes a patent litigation against You or any other
entity (including a cross-claim or counterclaim in a lawsuit)
alleging that Your Contribution, or the Work to which You have
contributed, constitutes direct or contributory patent
infringement, any patent licenses granted to that entity under
this Agreement for that Contribution or Work shall terminate as
of the date such litigation is filed.
4. You Have the Right to Grant Licenses to StrongLoop
You represent that You are legally entitled to grant the licenses
in this Agreement.
If Your employer(s) has rights to intellectual property that You
create, You represent that You have received permission to make
the Contributions on behalf of that employer, that Your employer
has waived such rights for Your Contributions, or that Your
employer has executed a separate Corporate Contributor License
Agreement with StrongLoop.
5. The Contributions Are Your Original Work
You represent that each of Your Contributions are Your original
works of authorship (see Section 8 (Submissions on Behalf of
Others) for submission on behalf of others). You represent that to
Your knowledge, no other person claims, or has the right to claim,
any right in any intellectual property right related to Your
Contributions.
You also represent that You are not legally obligated, whether by
entering into an agreement or otherwise, in any way that conflicts
with the terms of this Agreement.
You represent that Your Contribution submissions include complete
details of any third-party license or other restriction (including,
but not limited to, related patents and trademarks) of which You
are personally aware and which are associated with any part of
Your Contributions.
6. You Don't Have an Obligation to Provide Support for Your Contributions
You are not expected to provide support for Your Contributions,
except to the extent You desire to provide support. You may provide
support for free, for a fee, or not at all.
6. No Warranties or Conditions
StrongLoop acknowledges that unless required by applicable law or
agreed to in writing, You provide Your Contributions on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES
OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR
FITNESS FOR A PARTICULAR PURPOSE.
7. Submission on Behalf of Others
If You wish to submit work that is not Your original creation, You
may submit it to StrongLoop separately from any Contribution,
identifying the complete details of its source and of any license
or other restriction (including, but not limited to, related
patents, trademarks, and license agreements) of which You are
personally aware, and conspicuously marking the work as
"Submitted on Behalf of a Third-Party: [named here]".
8. Agree to Notify of Change of Circumstances
You agree to notify StrongLoop of any facts or circumstances of
which You become aware that would make these representations
inaccurate in any respect. Email us at callback@strongloop.com.
git commit -s -m "feat: my commit message"
```
Also see the [Contributing to LoopBack](https://loopback.io/doc/en/contrib/code-contrib.html) to get you started.
[Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html
[Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml

View File

@ -1,9 +1,10 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*global module:false*/
'use strict';
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
@ -15,51 +16,38 @@ module.exports = function(grunt) {
'* Copyright (c) <%= grunt.template.today("yyyy") %> ' +
'<%= pkg.author.name %>;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
// Task configuration.
jshint: {
options: {
jshintrc: true
},
gruntfile: {
src: 'Gruntfile.js'
},
libTest: {
src: ['lib/**/*.js', 'test/**/*.js']
}
},
mochaTest: {
'integration': {
src: 'test/integration/*.js',
options: {
reporter: 'dot'
}
reporter: 'dot',
},
},
'integration-xml': {
src: 'test/integration/*.js',
options: {
reporter: 'xunit',
captureFile: 'xintegration.xml'
}
captureFile: 'xintegration.xml',
},
},
'unit': {
src: 'test/*.js',
options: {
reporter: 'dot'
}
reporter: 'dot',
},
},
'unit-xml': {
src: 'test/*.js',
options: {
reporter: 'xunit',
captureFile: 'xunit.xml'
}
}
}
captureFile: 'xunit.xml',
},
},
},
});
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-mocha-test');
grunt.loadNpmTasks('grunt-contrib-jshint');
// Default task.
grunt.registerTask('default', ['unit', 'integration']);

View File

@ -1,4 +1,4 @@
Copyright (c) IBM Corp. 2014,2017. All Rights Reserved.
Copyright (c) IBM Corp. 2014,2018. All Rights Reserved.
Node module: loopback-connector-remote
This project is licensed under the MIT License, full text below.

View File

@ -1,5 +1,14 @@
# loopback-connector-remote
**THIS CONNECTOR DOES NOT SUPPORT LOOPBACK 4**
**⚠️ LoopBack 3 has reached end of life. We are no longer accepting pull requests or providing
support for community users. The only exception is fixes for critical bugs and security
vulnerabilities provided as part of support for IBM API Connect customers.
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as soon as possible.
Learn more about
<a href="https://loopback.io/doc/en/contrib/Long-term-support.html">LoopBack's long term support policy.</a>**
The remote connector enables you to use a LoopBack application as a data source via REST.
You can use the remote connector with a LoopBack application, a Node application, or a browser-based application that uses [LoopBack in the client](LoopBack-in-the-client.html).
The connector uses [Strong Remoting](Strong-Remoting.html).
@ -7,6 +16,7 @@ The connector uses [Strong Remoting](Strong-Remoting.html).
In general, using the remote connector is more convenient than calling into REST API, and enables you to switch the transport later if you need to.
Use loopback-connector-remote:
- Version 3.x with LoopBack v3 and later.
- Prior versions with LoopBack v2.

View File

@ -1,6 +0,0 @@
// Copyright IBM Corp. 2014. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = require('./lib/remote-connector');

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
@ -8,8 +8,8 @@
/*!
* Dependencies
*/
var relation = require('loopback-datasource-juggler/lib/relation-definition');
var RelationDefinition = relation.RelationDefinition;
const relation = require('loopback-datasource-juggler/lib/relation-definition');
const RelationDefinition = relation.RelationDefinition;
module.exports = RelationMixin;
@ -75,7 +75,7 @@ function RelationMixin() {
* @property {Object} model Model object
*/
RelationMixin.hasMany = function hasMany(modelTo, params) {
var def = RelationDefinition.hasMany(this, modelTo, params);
const def = RelationDefinition.hasMany(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
@ -139,7 +139,7 @@ RelationMixin.hasMany = function hasMany(modelTo, params) {
*
*/
RelationMixin.belongsTo = function(modelTo, params) {
var def = RelationDefinition.belongsTo(this, modelTo, params);
const def = RelationDefinition.belongsTo(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
@ -179,31 +179,31 @@ RelationMixin.belongsTo = function(modelTo, params) {
*/
RelationMixin.hasAndBelongsToMany =
function hasAndBelongsToMany(modelTo, params) {
var def = RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
const def = RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
RelationMixin.hasOne = function hasOne(modelTo, params) {
var def = RelationDefinition.hasOne(this, modelTo, params);
const def = RelationDefinition.hasOne(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
RelationMixin.referencesMany = function referencesMany(modelTo, params) {
var def = RelationDefinition.referencesMany(this, modelTo, params);
const def = RelationDefinition.referencesMany(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
RelationMixin.embedsOne = function embedsOne(modelTo, params) {
var def = RelationDefinition.embedsOne(this, modelTo, params);
const def = RelationDefinition.embedsOne(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
RelationMixin.embedsMany = function embedsMany(modelTo, params) {
var def = RelationDefinition.embedsMany(this, modelTo, params);
const def = RelationDefinition.embedsMany(this, modelTo, params);
this.dataSource.adapter.resolve(this);
defineRelationProperty(this, def);
};
@ -211,10 +211,23 @@ RelationMixin.embedsMany = function embedsMany(modelTo, params) {
function defineRelationProperty(modelClass, def) {
Object.defineProperty(modelClass.prototype, def.name, {
get: function() {
var that = this;
var scope = function() {
return that['__get__' + def.name].apply(that, arguments);
const that = this;
const scope = function() {
const cachedEntities = that.__cachedRelations &&
that.__cachedRelations[def.name];
if (arguments.length || !cachedEntities) {
return that['__get__' + def.name].apply(that, arguments);
}
// return the cached data
if (Array.isArray(cachedEntities)) {
return cachedEntities.map(data => new def.modelTo(data));
} else {
return new def.modelTo(cachedEntities);
}
};
scope.count = function() {
return that['__count__' + def.name].apply(that, arguments);
};
@ -231,6 +244,6 @@ function defineRelationProperty(modelClass, def) {
return that['__findById__' + def.name].apply(that, arguments);
};
return scope;
}
},
});
}

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
@ -9,12 +9,14 @@
* Dependencies.
*/
var assert = require('assert');
var remoting = require('strong-remoting');
var utils = require('loopback-datasource-juggler/lib/utils');
var jutil = require('loopback-datasource-juggler/lib/jutil');
var RelationMixin = require('./relations');
var InclusionMixin = require('loopback-datasource-juggler/lib/include');
const assert = require('assert');
const remoting = require('strong-remoting');
const utils = require('loopback-datasource-juggler/lib/utils');
const jutil = require('loopback-datasource-juggler/lib/jutil');
const RelationMixin = require('./relations');
const InclusionMixin = require('loopback-datasource-juggler/lib/include');
const findMethodNames = ['findById', 'findOne'];
/**
* Export the RemoteConnector class.
@ -29,7 +31,7 @@ module.exports = RemoteConnector;
function RemoteConnector(settings) {
assert(typeof settings ===
'object',
'cannot initialize RemoteConnector without a settings object');
'cannot initialize RemoteConnector without a settings object');
this.client = settings.client;
this.adapter = settings.adapter || 'rest';
this.protocol = settings.protocol || 'http';
@ -46,9 +48,8 @@ function RemoteConnector(settings) {
}
// handle mixins in the define() method
var DAO = this.DataAccessObject = function() {
const DAO = this.DataAccessObject = function() {
};
}
RemoteConnector.prototype.connect = function() {
@ -56,64 +57,95 @@ RemoteConnector.prototype.connect = function() {
};
RemoteConnector.initialize = function(dataSource, callback) {
var connector = dataSource.connector =
const connector = dataSource.connector =
new RemoteConnector(dataSource.settings);
connector.connect();
process.nextTick(callback);
};
RemoteConnector.prototype.define = function(definition) {
var Model = definition.model;
var remotes = this.remotes;
const Model = definition.model;
const remotes = this.remotes;
assert(Model.sharedClass,
'cannot attach ' +
'cannot attach ' +
Model.modelName +
' to a remote connector without a Model.sharedClass');
jutil.mixin(Model, RelationMixin);
jutil.mixin(Model, InclusionMixin);
remotes.addClass(Model.sharedClass);
this.resolve(Model);
this.setupRemotingTypeFor(Model);
};
RemoteConnector.prototype.resolve = function(Model) {
var remotes = this.remotes;
const remotes = this.remotes;
Model.sharedClass.methods().forEach(function(remoteMethod) {
if (remoteMethod.name !== 'Change' && remoteMethod.name !== 'Checkpoint') {
createProxyMethod(Model, remotes, remoteMethod);
}
});
};
RemoteConnector.prototype.setupRemotingTypeFor = function(Model) {
const remotes = this.remotes;
// setup a remoting type converter for this model
remotes.defineObjectType(Model.modelName, function(data) {
return new Model(data);
const model = new Model(data);
// process cached relations
if (model.__cachedRelations) {
for (const relation in model.__cachedRelations) {
const relatedModel = model.__cachedRelations[relation];
model.__data[relation] = relatedModel;
}
}
return model;
});
};
function createProxyMethod(Model, remotes, remoteMethod) {
var scope = remoteMethod.isStatic ? Model : Model.prototype;
var original = scope[remoteMethod.name];
const scope = remoteMethod.isStatic ? Model : Model.prototype;
const original = scope[remoteMethod.name];
function remoteMethodProxy() {
var args = Array.prototype.slice.call(arguments);
var lastArgIsFunc = typeof args[args.length - 1] === 'function';
var callback;
const args = Array.prototype.slice.call(arguments);
const lastArgIsFunc = typeof args[args.length - 1] === 'function';
let callback;
if (lastArgIsFunc) {
callback = args.pop();
} else {
callback = utils.createPromiseCallback();
}
const callbackPromise = callback.promise;
if (findMethodNames.includes(remoteMethod.name)) {
callback = proxy404toNull(callback);
}
if (remoteMethod.isStatic) {
remotes.invoke(remoteMethod.stringName, args, callback);
} else {
var ctorArgs = [this.id];
const ctorArgs = [this.id];
remotes.invoke(remoteMethod.stringName, ctorArgs, args, callback);
}
return callback.promise;
return callbackPromise;
}
function proxy404toNull(cb) {
return function(err, data) {
if (err && err.code === 'MODEL_NOT_FOUND') {
cb(null, null);
return;
}
cb(err, data);
};
}
scope[remoteMethod.name] = remoteMethodProxy;

View File

@ -1,8 +1,8 @@
{
"name": "loopback-connector-remote",
"version": "3.2.0",
"version": "3.4.1",
"description": "Remote REST API connector for Loopback",
"main": "index.js",
"main": "lib/remote-connector.js",
"keywords": [
"web",
"restful",
@ -10,10 +10,13 @@
"StrongLoop"
],
"scripts": {
"test": "grunt"
"test": "grunt",
"posttest": "npm run lint",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"engines": {
"node": ">=4.0.0"
"node": ">=8"
},
"repository": {
"type": "git",
@ -30,18 +33,21 @@
"homepage": "http://loopback.io",
"dependencies": {
"loopback-datasource-juggler": "^3.0.0",
"strong-remoting": "^3.0.0"
"strong-remoting": "^3.15.0"
},
"devDependencies": {
"assert": "^1.1.2",
"bluebird": "^3.3.5",
"grunt": "^1.0.1",
"assert": "^1.4.1",
"bluebird": "^3.5.1",
"eslint": "^6.5.1",
"eslint-config-loopback": "^13.1.0",
"grunt": "^1.0.3",
"grunt-cli": "^1.2.0",
"grunt-contrib-jshint": "^1.0.0",
"grunt-mocha-test": "^0.12.7",
"grunt-mocha-test": "^0.13.3",
"loopback": "^3.0.0",
"mocha": "^3.0.2",
"strong-task-emitter": "^0.0.7"
"mocha": "^5.2.0",
"sinon": "^6.0.0",
"strong-task-emitter": "^0.0.8"
},
"optionalDependencies": {}
"optionalDependencies": {},
"author": "IBM Corp."
}

View File

@ -1,30 +1,28 @@
// Copyright IBM Corp. 2016. All Rights Reserved.
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var extend = require('util')._extend;
var loopback = require('loopback');
var remoteConnector = require('..');
const extend = require('util')._extend;
const loopback = require('loopback');
const remoteConnector = require('..');
exports.createMemoryDataSource = createMemoryDataSource;
exports.createModel = createModel;
exports.createRemoteDataSource = createRemoteDataSource;
exports.createRemoteDataSourceWithOptions = createRemoteDataSourceWithOptions;
exports.createRestAppAndListen = createRestAppAndListen;
exports.getUserProperties = getUserProperties;
function createRestAppAndListen() {
var app = loopback();
const app = loopback({localRegistry: true});
app.set('host', '127.0.0.1');
app.set('port', 0);
app.set('legacyExplorer', false);
app.set('remoting', {
errorHandler: { debug: true, log: false },
errorHandler: {debug: true, log: false},
context: false,
});
@ -34,38 +32,17 @@ function createRestAppAndListen() {
return app;
}
function createMemoryDataSource() {
return loopback.createDataSource({connector: 'memory'});
function createMemoryDataSource(app) {
return app.dataSource('db', {connector: 'memory'});
}
function createRemoteDataSource(remoteApp) {
return loopback.createDataSource({
url: 'http://' + remoteApp.get('host') + ':' + remoteApp.get('port'),
connector: remoteConnector
});
}
function createRemoteDataSourceWithOptions(remoteApp, options) {
return loopback.createDataSource({
url: 'http://anyURL.com',
function createRemoteDataSource(app, serverApp) {
return app.dataSource('remote', {
url: 'http://' + serverApp.get('host') + ':' + serverApp.get('port'),
connector: remoteConnector,
options: options
});
}
/**
* Used to create models based on a set of options. May associate or link to an
* app.
*/
function createModel(options) {
var modelOptions = extend({ forceId: false }, options.options);
var Model = loopback.PersistedModel.extend(options.parent, options.properties,
modelOptions);
if (options.app) options.app.model(Model);
if (options.datasource) Model.attachTo(options.datasource);
return Model;
}
function getUserProperties() {
return {
'first': String,
@ -74,6 +51,6 @@ function getUserProperties() {
'password': String,
'gender': String,
'domain': String,
'email': String
'email': String,
};
}

View File

@ -1,9 +1,16 @@
var assert = require('assert');
var helper = require('../helper');
var Promise = require('bluebird');
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var globalPromiseSetManually = false;
var User;
'use strict';
const assert = require('assert');
const helper = require('../helper');
const Promise = require('bluebird');
let globalPromiseSetManually = false;
let User;
describe('promise support', function() {
before(setGlobalPromise);
@ -12,21 +19,21 @@ describe('promise support', function() {
context('create', function() {
it('supports promises', function() {
var retval = User.create();
const retval = User.create();
assert(retval && typeof retval.then === 'function');
});
});
context('find', function() {
it('supports promises', function() {
var retval = User.find();
const retval = User.find();
assert(retval && typeof retval.then === 'function');
});
});
context('findById', function() {
it('supports promises', function() {
var retval = User.findById(1);
const retval = User.findById(1);
assert(retval && typeof retval.then === 'function');
});
});
@ -40,12 +47,15 @@ function setGlobalPromise() {
}
function createUserModel() {
User = helper.createModel({
parent: 'user',
app: helper.createRestAppAndListen(),
datasource: helper.createMemoryDataSource(),
properties: helper.getUserProperties()
const app = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
User = app.registry.createModel({
name: 'user',
properties: helper.getUserProperties(),
options: {forceId: false},
});
app.model(User, {dataSource: db});
}
function resetGlobalPromise() {

View File

@ -0,0 +1,77 @@
// Copyright IBM Corp. 2018,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const helper = require('./helper');
const loopback = require('loopback');
const sinon = require('sinon');
const relation = require('loopback-datasource-juggler/lib/relation-definition');
const RelationTypes = relation.RelationTypes;
describe('Models Define Type Tests', function() {
let serverApp, clientApp, remoteDs, defineObjectTypeSpy, ChildModel;
beforeEach('create remote datasource', () => {
serverApp = helper.createRestAppAndListen();
clientApp = loopback({localRegistry: true});
remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
});
beforeEach('spy remote connector', () => {
defineObjectTypeSpy = sinon.spy(remoteDs.connector.remotes,
'defineObjectType');
});
afterEach('restore remote connector', () => {
defineObjectTypeSpy.restore();
});
it('should define a type of a remote model only once (no relations)', () => {
const RemoteModel = clientApp.registry.createModel({
name: 'RemoteModel',
});
clientApp.model(RemoteModel, {dataSource: remoteDs});
sinon.assert.calledOnce(defineObjectTypeSpy.withArgs(
RemoteModel.modelName,
));
});
describe('when a child model is created', () => {
beforeEach('create a child model', () => {
ChildModel = clientApp.registry.createModel({
name: 'ChildModel',
});
clientApp.model(ChildModel, {dataSource: remoteDs});
});
Object.getOwnPropertyNames(RelationTypes).forEach((relationKey) => {
const relation = RelationTypes[relationKey];
it('should define a type of a remote model only once (' + relation + ')',
() => {
const RemoteModel = clientApp.registry.createModel({
name: 'RemoteModel' + relation,
relations: {
children: {
type: relation,
model: 'ChildModel',
},
},
});
clientApp.model(RemoteModel, {dataSource: remoteDs});
sinon.assert.calledOnce(defineObjectTypeSpy.withArgs(
RemoteModel.modelName,
));
sinon.assert.calledOnce(defineObjectTypeSpy.withArgs(
ChildModel.modelName,
));
});
});
});
});

View File

@ -1,65 +1,69 @@
// Copyright IBM Corp. 2016. All Rights Reserved.
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var assert = require('assert');
var helper = require('./helper');
var TaskEmitter = require('strong-task-emitter');
const assert = require('assert');
const helper = require('./helper');
const loopback = require('loopback');
const TaskEmitter = require('strong-task-emitter');
describe('Model tests', function() {
var User;
let User;
beforeEach(function() {
User = helper.createModel({
parent: 'user',
app: helper.createRestAppAndListen(),
datasource: helper.createMemoryDataSource(),
properties: helper.getUserProperties()
const app = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
User = app.registry.createModel({
name: 'user',
properties: helper.getUserProperties(),
options: {forceId: false},
});
app.model(User, {dataSource: db});
});
describe('Model.validatesPresenceOf(properties...)', function() {
it('should require a model to include a property to be considered valid',
function() {
User.validatesPresenceOf('first', 'last', 'age');
var joe = new User({first: 'joe'});
assert(joe.isValid() === false, 'model should not validate');
assert(joe.errors.last, 'should have a missing last error');
assert(joe.errors.age, 'should have a missing age error');
});
function() {
User.validatesPresenceOf('first', 'last', 'age');
const joe = new User({first: 'joe'});
assert(joe.isValid() === false, 'model should not validate');
assert(joe.errors.last, 'should have a missing last error');
assert(joe.errors.age, 'should have a missing age error');
});
});
describe('Model.validatesLengthOf(property, options)', function() {
it('should require a property length to be within a specified range',
function() {
User.validatesLengthOf('password', {min: 5, message: {min:
function() {
User.validatesLengthOf('password', {min: 5, message: {min:
'Password is too short'}});
var joe = new User({password: '1234'});
assert(joe.isValid() === false, 'model should not be valid');
assert(joe.errors.password, 'should have password error');
});
const joe = new User({password: '1234'});
assert(joe.isValid() === false, 'model should not be valid');
assert(joe.errors.password, 'should have password error');
});
});
describe('Model.validatesInclusionOf(property, options)', function() {
it('should require a value for `property` to be in the specified array',
function() {
User.validatesInclusionOf('gender', {in: ['male', 'female']});
var foo = new User({gender: 'bar'});
assert(foo.isValid() === false, 'model should not be valid');
assert(foo.errors.gender, 'should have gender error');
});
function() {
User.validatesInclusionOf('gender', {in: ['male', 'female']});
const foo = new User({gender: 'bar'});
assert(foo.isValid() === false, 'model should not be valid');
assert(foo.errors.gender, 'should have gender error');
});
});
describe('Model.validatesExclusionOf(property, options)', function() {
it('should require a value for `property` to not exist in the specified ' +
'array', function() {
User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
var foo = new User({domain: 'www'});
var bar = new User({domain: 'billing'});
var bat = new User({domain: 'admin'});
const foo = new User({domain: 'www'});
const bar = new User({domain: 'billing'});
const bat = new User({domain: 'admin'});
assert(foo.isValid() === false);
assert(bar.isValid() === false);
assert(bat.isValid() === false);
@ -73,9 +77,9 @@ describe('Model tests', function() {
it('should require a value for `property` to be a specific type of ' +
'`Number`', function() {
User.validatesNumericalityOf('age', {int: true});
var joe = new User({age: 10.2});
const joe = new User({age: 10.2});
assert(joe.isValid() === false);
var bob = new User({age: 0});
const bob = new User({age: 0});
assert(bob.isValid() === true);
assert(joe.errors.age, 'model should have an age error');
});
@ -84,15 +88,15 @@ describe('Model tests', function() {
describe('myModel.isValid()', function() {
it('should validate the model instance', function() {
User.validatesNumericalityOf('age', {int: true});
var user = new User({first: 'joe', age: 'flarg'});
var valid = user.isValid();
const user = new User({first: 'joe', age: 'flarg'});
const valid = user.isValid();
assert(valid === false);
assert(user.errors.age, 'model should have age error');
});
it('should validate the model asynchronously', function(done) {
User.validatesNumericalityOf('age', {int: true});
var user = new User({first: 'joe', age: 'flarg'});
const user = new User({first: 'joe', age: 'flarg'});
user.isValid(function(valid) {
assert(valid === false);
assert(user.errors.age, 'model should have age error');
@ -103,63 +107,63 @@ describe('Model tests', function() {
describe('Model.create([data], [callback])', function() {
it('should create an instance and save to the attached data source',
function(done) {
User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof User);
done();
function(done) {
User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof User);
done();
});
});
});
});
describe('model.save([options], [callback])', function() {
it('should save an instance of a Model to the attached data source',
function(done) {
var joe = new User({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
function(done) {
const joe = new User({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
});
});
});
});
describe('model.updateAttributes(data, [callback])', function() {
it('should save specified attributes to the attached data source',
function(done) {
User.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast'
}, function(err, updatedUser) {
function(done) {
User.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
assert.equal(user.first, 'joe');
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast',
}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
});
});
});
});
});
describe('Model.upsert(data, callback)', function() {
it('should update when a record with id=data.id is found, insert otherwise',
function(done) {
User.upsert({first: 'joe', id: 7}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
User.upsert({first: 'bob', id: 7}, function(err, updatedUser) {
function(done) {
User.upsert({first: 'joe', id: 7}, function(err, user) {
if (err) return done(err);
assert.equal(updatedUser.first, 'bob');
done();
assert.equal(user.first, 'joe');
User.upsert({first: 'bob', id: 7}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'bob');
done();
});
});
});
});
});
describe('model.destroy([callback])', function() {
@ -184,19 +188,19 @@ describe('Model tests', function() {
describe('Model.deleteById(id, [callback])', function() {
it('should delete a model instance from the attached data source',
function(done) {
User.create({first: 'joe', last: 'bob'}, function(err, user) {
if (err) return done(err);
User.deleteById(user.id, function(err) {
function(done) {
User.create({first: 'joe', last: 'bob'}, function(err, user) {
if (err) return done(err);
User.findById(user.id, function(err, notFound) {
User.deleteById(user.id, function(err) {
if (err) return done(err);
assert.equal(notFound, null);
done();
User.findById(user.id, function(err, notFound) {
if (err) return done(err);
assert.equal(notFound, null);
done();
});
});
});
});
});
});
describe('Model.findById(id, callback)', function() {
@ -217,21 +221,21 @@ describe('Model tests', function() {
describe('Model.count([query], callback)', function() {
it('should return the count of Model instances in data source',
function(done) {
var taskEmitter = new TaskEmitter();
taskEmitter
.task(User, 'create', {first: 'jill', age: 100})
.task(User, 'create', {first: 'bob', age: 200})
.task(User, 'create', {first: 'jan'})
.task(User, 'create', {first: 'sam'})
.task(User, 'create', {first: 'suzy'})
.on('done', function() {
User.count({age: {gt: 99}}, function(err, count) {
if (err) return done(err);
assert.equal(count, 2);
done();
function(done) {
const taskEmitter = new TaskEmitter();
taskEmitter
.task(User, 'create', {first: 'jill', age: 100})
.task(User, 'create', {first: 'bob', age: 200})
.task(User, 'create', {first: 'jan'})
.task(User, 'create', {first: 'sam'})
.task(User, 'create', {first: 'suzy'})
.on('done', function() {
User.count({age: {gt: 99}}, function(err, count) {
if (err) return done(err);
assert.equal(count, 2);
done();
});
});
});
});
});
});
});

View File

@ -1,47 +1,49 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var assert = require('assert');
var helper = require('./helper');
const assert = require('assert');
const helper = require('./helper');
const loopback = require('loopback');
describe('RemoteConnector', function() {
var ctx = this;
let serverApp, clientApp, ServerModel, ClientModel;
before(function setupServer(done) {
ctx.serverApp = helper.createRestAppAndListen();
ctx.ServerModel = helper.createModel({
parent: 'TestModel',
app: ctx.serverApp,
datasource: helper.createMemoryDataSource()
const app = serverApp = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
ServerModel = app.registry.createModel({
name: 'TestModel',
});
ctx.serverApp.locals.handler.on('listening', function() { done(); });
app.model(ServerModel, {dataSource: db});
app.locals.handler.on('listening', function() { done(); });
});
before(function setupRemoteClient(done) {
ctx.remoteApp = helper.createRestAppAndListen();
ctx.RemoteModel = helper.createModel({
parent: 'TestModel',
app: ctx.remoteApp,
datasource: helper.createRemoteDataSource(ctx.serverApp)
before(function setupRemoteClient() {
const app = clientApp = loopback({localRegistry: true});
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
ClientModel = app.registry.createModel({
name: 'TestModel',
});
ctx.remoteApp.locals.handler.on('listening', function() { done(); });
app.model(ClientModel, {dataSource: remoteDs});
});
after(function() {
ctx.serverApp.locals.handler.close();
ctx.remoteApp.locals.handler.close();
ctx.ServerModel = null;
ctx.RemoteModel = null;
serverApp.locals.handler.close();
ServerModel = null;
ClientModel = null;
});
it('should support the save method', function(done) {
var calledServerCreate = false;
let calledServerCreate = false;
ctx.ServerModel.create = function(data, options, cb, callback) {
ServerModel.create = function(data, options, cb, callback) {
if (typeof options === 'function') {
callback = cb;
cb = options;
@ -54,20 +56,20 @@ describe('RemoteConnector', function() {
else cb(null, data);
};
var m = new ctx.RemoteModel({foo: 'bar'});
const m = new ClientModel({foo: 'bar'});
m.save(function(err, instance) {
if (err) return done(err);
assert(instance);
assert(instance instanceof ctx.RemoteModel);
assert(instance instanceof ClientModel);
assert(calledServerCreate);
done();
});
});
it('should support aliases', function(done) {
var calledServerUpsert = false;
ctx.ServerModel.patchOrCreate =
ctx.ServerModel.upsert = function(id, options, cb) {
let calledServerUpsert = false;
ServerModel.patchOrCreate =
ServerModel.upsert = function(id, options, cb) {
if (typeof options === 'function') {
cb = options;
options = {};
@ -77,10 +79,10 @@ describe('RemoteConnector', function() {
cb();
};
ctx.RemoteModel.updateOrCreate({}, function(err, instance) {
ClientModel.updateOrCreate({}, function(err, instance) {
if (err) return done(err);
assert(instance);
assert(instance instanceof ctx.RemoteModel);
assert(instance instanceof ClientModel);
assert(calledServerUpsert, 'server upsert should have been called');
done();
});
@ -88,46 +90,45 @@ describe('RemoteConnector', function() {
});
describe('Custom Path', function() {
var ctx = this;
let serverApp, clientApp, ServerModel, ClientModel;
before(function setupServer(done) {
ctx.serverApp = helper.createRestAppAndListen();
ctx.ServerModel = helper.createModel({
parent: 'TestModel',
app: ctx.serverApp,
datasource: helper.createMemoryDataSource(),
const app = serverApp = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
ServerModel = app.registry.createModel({
name: 'TestModel',
options: {
http: {path: '/custom'}
}
http: {path: '/custom'},
},
});
ctx.serverApp.locals.handler.on('listening', function() { done(); });
app.model(ServerModel, {dataSource: db});
serverApp.locals.handler.on('listening', function() { done(); });
});
before(function setupRemoteClient(done) {
ctx.remoteApp = helper.createRestAppAndListen();
ctx.RemoteModel = helper.createModel({
parent: 'TestModel',
app: ctx.remoteApp,
datasource: helper.createRemoteDataSource(ctx.serverApp),
before(function setupRemoteClient() {
const app = clientApp = loopback({localRegistry: true});
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
ClientModel = app.registry.createModel({
name: 'TestModel',
options: {
dataSource: 'remote',
http: {path: '/custom'}
}
http: {path: '/custom'},
},
});
ctx.remoteApp.locals.handler.on('listening', function() { done(); });
app.model(ClientModel, {dataSource: remoteDs});
});
after(function(done)
{
ctx.serverApp.locals.handler.close();
ctx.remoteApp.locals.handler.close();
ctx.ServerModel = null;
ctx.RemoteModel = null;
done();
after(function() {
serverApp.locals.handler.close();
ServerModel = null;
ClientModel = null;
});
it('should support http.path configuration', function(done) {
ctx.RemoteModel.create({}, function(err, instance) {
ClientModel.create({}, function(err, instance) {
if (err) return done(err);
assert(instance);
done();
@ -137,12 +138,13 @@ describe('Custom Path', function() {
describe('RemoteConnector with options', () => {
it('should have the remoting options passed to the remote object', () => {
const serverApp = helper.createRestAppAndListen();
const app = loopback();
const dataSource = app.dataSource('remote', {
url: 'http://example.com',
connector: require('..'),
options: {'test': 'abc'},
});
const datasource = helper.createRemoteDataSourceWithOptions(
serverApp,
{'test': 'abc'});
assert.deepEqual(datasource.connector.remotes.options, {test: 'abc'});
assert.deepEqual(dataSource.connector.remotes.options, {test: 'abc'});
});
});

View File

@ -1,161 +1,389 @@
// Copyright IBM Corp. 2016. All Rights Reserved.
// Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback-connector-remote
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var assert = require('assert');
var helper = require('./helper');
var TaskEmitter = require('strong-task-emitter');
const assert = require('assert');
const helper = require('./helper');
const loopback = require('loopback');
const TaskEmitter = require('strong-task-emitter');
describe('Remote model tests', function() {
var ctx = this;
let serverApp, ServerModel, ServerRelatedModel, ServerModelWithSingleChild,
clientApp, ClientModel, ClientRelatedModel, ClientModelWithSingleChild;
beforeEach(function(done) {
ctx.serverApp = helper.createRestAppAndListen();
ctx.ServerModel = helper.createModel({
parent: 'TestModel',
app: ctx.serverApp,
datasource: helper.createMemoryDataSource(),
properties: helper.userProperties
beforeEach(function setupServer(done) {
const app = serverApp = helper.createRestAppAndListen();
const db = helper.createMemoryDataSource(app);
ServerModel = app.registry.createModel({
name: 'TestModel',
properties: helper.getUserProperties(),
options: {
forceId: false,
relations: {
children: {
type: 'hasMany',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
},
});
ctx.serverApp.locals.handler.on('listening', function() { done(); });
ServerModelWithSingleChild = app.registry.createModel({
name: 'TestModelWithSingleChild',
properties: helper.getUserProperties(),
options: {
forceId: false,
relations: {
child: {
type: 'hasOne',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
},
});
ServerRelatedModel = app.registry.createModel({
name: 'ChildModel',
properties: {
note: {type: 'text'},
parentId: {type: 'number'},
},
options: {forceId: false},
});
app.model(ServerModel, {dataSource: db});
app.model(ServerRelatedModel, {dataSource: db});
app.model(ServerModelWithSingleChild, {dataSource: db});
serverApp.locals.handler.on('listening', function() { done(); });
});
beforeEach(function setupRemoteClient(done) {
ctx.remoteApp = helper.createRestAppAndListen();
ctx.RemoteModel = helper.createModel({
parent: 'TestModel',
app: ctx.remoteApp,
datasource: helper.createRemoteDataSource(ctx.serverApp),
properties: helper.userProperties
beforeEach(function setupRemoteClient() {
const app = clientApp = loopback({localRegistry: true});
const remoteDs = helper.createRemoteDataSource(clientApp, serverApp);
ClientRelatedModel = app.registry.createModel({
name: 'ChildModel',
properties: {
note: {type: 'text'},
parentId: {type: 'number'},
},
options: {
strict: true,
},
});
ctx.remoteApp.locals.handler.on('listening', function() { done(); });
ClientModel = app.registry.createModel({
name: 'TestModel',
properties: helper.getUserProperties(),
options: {
relations: {
children: {
type: 'hasMany',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
strict: true,
},
});
ClientModelWithSingleChild = app.registry.createModel({
name: 'TestModelWithSingleChild',
properties: helper.getUserProperties(),
options: {
relations: {
child: {
type: 'hasOne',
model: 'ChildModel',
foreignKey: 'parentId',
},
},
strict: true,
},
});
app.model(ClientModel, {dataSource: remoteDs});
app.model(ClientRelatedModel, {dataSource: remoteDs});
app.model(ClientModelWithSingleChild, {dataSource: remoteDs});
});
afterEach(function() {
ctx.serverApp.locals.handler.close();
ctx.remoteApp.locals.handler.close();
ctx.ServerModel = null;
ctx.RemoteModel = null;
serverApp.locals.handler.close();
ServerModel = null;
ServerRelatedModel = null;
ClientModel = null;
});
describe('Model.create([data], [callback])', function() {
it('should create an instance and save to the attached data source',
function(done) {
ctx.RemoteModel.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof ctx.RemoteModel);
done();
function(done) {
ClientModel.create({first: 'Joe', last: 'Bob'}, function(err, user) {
if (err) return done(err);
assert(user instanceof ClientModel);
done();
});
});
});
});
describe('model.save([options], [callback])', function() {
it('should save an instance of a Model to the attached data source',
function(done) {
var joe = new ctx.RemoteModel({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
function(done) {
const joe = new ClientModel({first: 'Joe', last: 'Bob'});
joe.save(function(err, user) {
if (err) return done(err);
assert(user.id);
assert(!user.errors);
done();
});
});
});
});
describe('model.updateAttributes(data, [callback])', function() {
it('should save specified attributes to the attached data source',
function(done) {
ctx.ServerModel.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast'
}, function(err, updatedUser) {
function(done) {
ServerModel.create({first: 'joe', age: 100}, function(err, user) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
assert.equal(user.first, 'joe');
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast',
}, function(err, updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'updatedFirst');
assert.equal(updatedUser.last, 'updatedLast');
assert.equal(updatedUser.age, 100);
done();
});
});
});
});
});
describe('Model.upsert(data, callback)', function() {
it('should update when a record with id=data.id is found, insert otherwise',
function(done) {
ctx.RemoteModel.upsert({first: 'joe', id: 7}, function(err, user) {
if (err) return done(err);
assert.equal(user.first, 'joe');
ctx.RemoteModel.upsert({first: 'bob', id: 7}, function(err,
updatedUser) {
function(done) {
ClientModel.upsert({first: 'joe', id: 7}, function(err, user) {
if (err) return done(err);
assert.equal(updatedUser.first, 'bob');
done();
assert.equal(user.first, 'joe');
ClientModel.upsert({first: 'bob', id: 7}, function(err,
updatedUser) {
if (err) return done(err);
assert.equal(updatedUser.first, 'bob');
done();
});
});
});
});
});
describe('Model.deleteById(id, [callback])', function() {
it('should delete a model instance from the attached data source',
function(done) {
ctx.ServerModel.create({first: 'joe', last: 'bob'}, function(err, user) {
if (err) return done(err);
ctx.RemoteModel.deleteById(user.id, function(err) {
function(done) {
ServerModel.create({first: 'joe', last: 'bob'}, function(err, user) {
if (err) return done(err);
ctx.RemoteModel.findById(user.id, function(err, notFound) {
assert.equal(notFound, null);
assert(err && err.statusCode === 404,
'should have failed with HTTP 404');
ClientModel.deleteById(user.id, function(err) {
if (err) return done(err);
ClientModel.findById(user.id, function(err, notFound) {
if (err) return done(err);
assert.equal(notFound, null);
done();
});
});
});
});
});
describe('Model.exists(id, callback)', function() {
it('should return true when the model with the given id exists',
function(done) {
ServerModel.create({first: 'max'}, function(err, user) {
if (err) return done(err);
ClientModel.exists(user.id, function(err, exist) {
if (err) return done(err);
assert.equal(exist, true);
done();
});
});
});
});
});
describe('Model.findById(id, callback)', function() {
it('should find an instance by id from the attached data source',
function(done) {
ctx.ServerModel.create({first: 'michael', last: 'jordan', id: 23},
function(err) {
if (err) return done(err);
ctx.RemoteModel.findById(23, function(err, user) {
it('should return false when there is no model with the given id',
function(done) {
ClientModel.exists('user-id-does-not-exist', function(err, exist) {
if (err) return done(err);
assert.equal(user.id, 23);
assert.equal(user.first, 'michael');
assert.equal(user.last, 'jordan');
assert.equal(exist, false);
done();
});
});
});
});
describe('Model.findById(id, callback)', function() {
it('should return null when an instance does not exist',
function(done) {
ClientModel.findById(23, function(err, notFound) {
if (err) return done(err);
assert.equal(notFound, null);
done();
});
});
it('should find an instance by id from the attached data source',
function(done) {
ServerModel.create({first: 'michael', last: 'jordan', id: 23},
function(err) {
if (err) return done(err);
ClientModel.findById(23, function(err, user) {
if (err) return done(err);
assert.equal(user.id, 23);
assert.equal(user.first, 'michael');
assert.equal(user.last, 'jordan');
done();
});
});
});
});
describe('Model.findOne([filter], callback)', function() {
it('should return null when an instance does not exist',
function(done) {
ClientModel.findOne({where: {id: 24}}, function(err, notFound) {
if (err) return done(err);
assert.equal(notFound, null);
done();
});
});
it('should find an instance from the attached data source',
function(done) {
ServerModel.create({first: 'keanu', last: 'reeves', id: 24},
function(err) {
if (err) return done(err);
ClientModel.findOne({where: {id: 24}}, function(err, user) {
if (err) return done(err);
assert.equal(user.id, 24);
assert.equal(user.first, 'keanu');
assert.equal(user.last, 'reeves');
done();
});
});
});
});
describe('Model.count([query], callback)', function() {
it('should return the count of Model instances from both data source',
function(done) {
var taskEmitter = new TaskEmitter();
taskEmitter
.task(ctx.ServerModel, 'create', {first: 'jill', age: 100})
.task(ctx.RemoteModel, 'create', {first: 'bob', age: 200})
.task(ctx.RemoteModel, 'create', {first: 'jan'})
.task(ctx.ServerModel, 'create', {first: 'sam'})
.task(ctx.ServerModel, 'create', {first: 'suzy'})
.on('done', function(err) {
if (err) return done(err);
ctx.RemoteModel.count({age: {gt: 99}}, function(err, count) {
function(done) {
const taskEmitter = new TaskEmitter();
taskEmitter
.task(ServerModel, 'create', {first: 'jill', age: 100})
.task(ClientModel, 'create', {first: 'bob', age: 200})
.task(ClientModel, 'create', {first: 'jan'})
.task(ServerModel, 'create', {first: 'sam'})
.task(ServerModel, 'create', {first: 'suzy'})
.on('done', function(err) {
if (err) return done(err);
assert.equal(count, 2);
done();
ClientModel.count({age: {gt: 99}}, function(err, count) {
if (err) return done(err);
assert.equal(count, 2);
done();
});
});
});
});
describe('Model find with include filter', function() {
let hasManyParent, hasManyChild, hasOneParent, hasOneChild;
beforeEach(givenSampleData);
it('should return also the included requested models', function() {
const parentId = hasManyParent.id;
return ClientModel.findById(hasManyParent.id, {include: 'children'})
.then(returnedUser => {
assert(returnedUser instanceof ClientModel);
const user = returnedUser.toJSON();
assert.equal(user.id, parentId);
assert.equal(user.first, hasManyParent.first);
assert(Array.isArray(user.children));
assert.equal(user.children.length, 1);
assert.deepEqual(user.children[0], hasManyChild.toJSON());
});
});
it('should return cachedRelated entity without call', function() {
const parentId = hasManyParent.id;
return ClientModel.findById(parentId, {include: 'children'})
.then(returnedUser => {
assert(returnedUser instanceof ClientModel);
const children = returnedUser.children();
assert.equal(returnedUser.id, parentId);
assert.equal(returnedUser.first, hasManyParent.first);
assert(Array.isArray(children));
assert.equal(children.length, 1);
assert(children[0] instanceof ClientRelatedModel);
assert.deepEqual(children[0].toJSON(), hasManyChild.toJSON());
});
});
it('should also work for single (non array) relations', function() {
const parentId = hasOneParent.id;
return ClientModelWithSingleChild.findById(parentId, {include: 'child'})
.then(returnedUser => {
assert(returnedUser instanceof ClientModelWithSingleChild);
const child = returnedUser.child();
assert.equal(returnedUser.id, parentId);
assert.equal(returnedUser.first, hasOneParent.first);
assert(child instanceof ClientRelatedModel);
assert.deepEqual(child.toJSON(), hasOneChild.toJSON());
});
});
function givenSampleData() {
return ServerModel.create({first: 'eiste', last: 'kopries'})
.then(parent => {
hasManyParent = parent;
return ServerRelatedModel.create({
note: 'mitsos',
parentId: parent.id,
id: 11,
});
})
.then(child => {
hasManyChild = child;
return ServerModelWithSingleChild.create({
first: 'mipos',
last: 'tora',
id: 12,
});
})
.then(parent => {
hasOneParent = parent;
return ServerRelatedModel.create({
note: 'mitsos3',
parentId: parent.id,
id: 13,
});
})
.then(child => {
hasOneChild = child;
});
}
});
describe('Model.updateAll([where], [data])', () => {
it('returns the count of updated instances in data source', async () => {
await ServerModel.create({first: 'baby', age: 1});
await ServerModel.create({first: 'grandma', age: 80});
const result = await ClientModel.updateAll(
{age: {lt: 6}},
{last: 'young'},
);
assert.deepEqual(result, {count: 1});
});
});
});