ki 0.4.10 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +16 -8
  3. data/MIDDLEWARE.md +27 -7
  4. data/README.md +45 -2
  5. data/REALTIME.md +48 -0
  6. data/TODO.md +19 -0
  7. data/ki.gemspec +1 -0
  8. data/lib/ki.rb +5 -0
  9. data/lib/ki/base_request.rb +7 -0
  10. data/lib/ki/channel_manager.rb +58 -0
  11. data/lib/ki/ki.rb +1 -0
  12. data/lib/ki/ki_cli.rb +1 -1
  13. data/lib/ki/ki_config.rb +18 -2
  14. data/lib/ki/middleware/api_handler.rb +11 -16
  15. data/lib/ki/middleware/helpers/redirect_to_helper.rb +13 -0
  16. data/lib/ki/middleware/realtime.rb +81 -0
  17. data/lib/ki/model.rb +6 -4
  18. data/lib/ki/modules/restrictions.rb +4 -7
  19. data/lib/ki/orm.rb +38 -2
  20. data/lib/ki/utils/redirect_to_helper.rb +0 -0
  21. data/lib/ki/version.rb +1 -1
  22. data/spec/examples/json.northpole.ro/.bowerrc +3 -0
  23. data/spec/examples/json.northpole.ro/Gemfile +1 -0
  24. data/spec/examples/json.northpole.ro/bower.json +17 -0
  25. data/spec/examples/json.northpole.ro/config.ru +3 -0
  26. data/spec/examples/json.northpole.ro/config.yml +4 -0
  27. data/spec/examples/json.northpole.ro/config.yml.backup +18 -0
  28. data/spec/examples/json.northpole.ro/config/deploy.rb +2 -1
  29. data/spec/examples/json.northpole.ro/config/deploy/production.rb +4 -38
  30. data/spec/examples/json.northpole.ro/public/app/.bowerrc +3 -0
  31. data/spec/examples/json.northpole.ro/public/app/.gitignore +9 -0
  32. data/spec/examples/json.northpole.ro/public/app/.jshintrc +13 -0
  33. data/spec/examples/json.northpole.ro/public/app/.travis.yml +14 -0
  34. data/spec/examples/json.northpole.ro/public/app/LICENSE +22 -0
  35. data/spec/examples/json.northpole.ro/public/app/README.md +82 -0
  36. data/spec/examples/json.northpole.ro/public/app/bower.json +20 -0
  37. data/spec/examples/json.northpole.ro/public/app/e2e/pages/ContactUser.js +22 -0
  38. data/spec/examples/json.northpole.ro/public/app/e2e/pages/UserDetails.js +11 -0
  39. data/spec/examples/json.northpole.ro/public/app/e2e/pages/UserList.js +12 -0
  40. data/spec/examples/json.northpole.ro/public/app/e2e/protractor.conf.js +26 -0
  41. data/spec/examples/json.northpole.ro/public/app/e2e/scenarios/users.js +34 -0
  42. data/spec/examples/json.northpole.ro/public/app/gulpfile.js +104 -0
  43. data/spec/examples/json.northpole.ro/public/app/karma.conf.js +35 -0
  44. data/spec/examples/json.northpole.ro/public/app/package.json +30 -0
  45. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/avatar-1.svg +11 -0
  46. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/avatar-4.svg +16 -0
  47. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/avatars.svg +244 -0
  48. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/google_plus.svg +1 -0
  49. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/hangouts.svg +1 -0
  50. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_fullscreen_48px.svg +4 -0
  51. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_fullscreen_exit_48px.svg +4 -0
  52. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_music_note_48px.svg +1 -0
  53. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_note_add_48px.svg +1 -0
  54. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_view_list_48px.svg +1 -0
  55. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/icon.svg +1 -0
  56. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/mail.svg +1 -0
  57. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/manggo.svg +1095 -0
  58. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/menu.svg +4 -0
  59. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/phone.svg +1 -0
  60. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/share.svg +3 -0
  61. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/twitter.svg +1 -0
  62. data/spec/examples/json.northpole.ro/public/app/src/css/app.css +138 -0
  63. data/spec/examples/json.northpole.ro/public/app/src/css/app.css.map +7 -0
  64. data/spec/examples/json.northpole.ro/public/app/src/css/app.sass +145 -0
  65. data/spec/examples/json.northpole.ro/public/app/src/index.html +157 -0
  66. data/spec/examples/json.northpole.ro/public/app/src/js/MainController.coffee +167 -0
  67. data/spec/examples/json.northpole.ro/public/app/src/js/app.coffee +58 -0
  68. data/spec/examples/json.northpole.ro/public/app/src/js/blobs/BlobsController.coffee +115 -0
  69. data/spec/examples/json.northpole.ro/public/app/src/js/blobs/blobs.html +48 -0
  70. data/spec/examples/json.northpole.ro/public/app/src/js/tutorial/tutorial.html +15 -0
  71. data/spec/examples/json.northpole.ro/public/app/src/js/users/UserService.coffee +12 -0
  72. data/spec/examples/json.northpole.ro/public/app/src/js/users/Users.coffee +1 -0
  73. data/spec/examples/json.northpole.ro/public/app/src/js/users/users.html +8 -0
  74. data/spec/examples/json.northpole.ro/public/javascripts/jnorthpole.coffee +40 -7
  75. data/spec/examples/json.northpole.ro/public/javascripts/realtime.coffee +28 -0
  76. data/spec/examples/json.northpole.ro/public/{javascripts/music → music}/angular-youtube-embed.js +0 -0
  77. data/spec/examples/json.northpole.ro/public/music/index.html +126 -0
  78. data/spec/examples/json.northpole.ro/public/music/music.coffee +99 -0
  79. data/spec/examples/json.northpole.ro/public/music/music.sass +63 -0
  80. data/spec/examples/json.northpole.ro/public/stylesheets/app.sass +3 -0
  81. data/spec/examples/json.northpole.ro/views/faq.haml +7 -0
  82. data/spec/examples/json.northpole.ro/views/layout.haml +1 -0
  83. data/spec/examples/json.northpole.ro/views/music.haml +2 -0
  84. data/spec/examples/json.northpole.ro/views/websocket.haml +7 -0
  85. data/spec/lib/ki/channel_manager_spec.rb +82 -0
  86. data/spec/lib/ki/helpers_spec.rb +11 -0
  87. data/spec/lib/ki/ki_config_spec.rb +28 -0
  88. data/spec/lib/ki/middleware/admin_generator_spec.rb +8 -0
  89. data/spec/lib/ki/middleware/init_middleware_spec.rb +21 -0
  90. data/spec/lib/ki/middleware/realtime_spec.rb +96 -0
  91. data/spec/lib/ki/model_spec.rb +28 -7
  92. data/spec/lib/ki/modules/model_helper_spec.rb +31 -0
  93. data/spec/lib/ki/orm_spec.rb +26 -0
  94. metadata +211 -9
  95. data/spec/examples/json.northpole.ro/public/javascripts/docs.min.js +0 -16
  96. data/spec/examples/json.northpole.ro/views/awsum.haml +0 -108
@@ -0,0 +1,28 @@
1
+ $(document).ready ->
2
+ receivedMessages = []
3
+
4
+ output = $('.realtime-output')
5
+ return if output.length <= 0
6
+
7
+ socket = jNorthPole.getNewRealtimeSocket((data) ->
8
+ console.log data
9
+ return unless data.data?
10
+ json = JSON.parse(data.data)
11
+ return unless json.messages?
12
+
13
+ for message in json.messages
14
+ if $.inArray(message.id, receivedMessages) == -1
15
+ receivedMessages.push(message.id)
16
+ output.append("#{message.content.message}<br />")
17
+ )
18
+ setTimeout ->
19
+ jNorthPole.subscribe(socket, 'jNorthPoleChat')
20
+ , 2000
21
+
22
+ $('.realtime-input').keypress((event) ->
23
+ keycode = if event.keyCode then event.keyCode else event.which
24
+ if (keycode == 13)
25
+ inptz = $(@)
26
+ jNorthPole.publish(socket, 'jNorthPoleChat', { message: inptz.val() })
27
+ inptz.val('')
28
+ )
@@ -0,0 +1,126 @@
1
+ <!DOCTYPE html>
2
+ <html ng-app="app">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width">
6
+ <title>music</title>
7
+
8
+ <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
9
+ <!-- Include roboto.css to use the Roboto web font, material.css to include the theme and ripples.css to style the ripple effect -->
10
+ <link href="/bootstrap-material-design/dist/css/roboto.min.css" rel="stylesheet">
11
+ <link href="/bootstrap-material-design/dist/css/material.min.css" rel="stylesheet">
12
+ <link href="/bootstrap-material-design/dist/css/ripples.min.css" rel="stylesheet">
13
+
14
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
15
+ <link rel="stylesheet" href="/music/music.css">
16
+ </head>
17
+ <body ng-controller="MainCtrl">
18
+ <div class="container">
19
+ <div class="row" ng-hide="!loggedIn">
20
+ <div class="col-md-12 well" style="display: flex; align-items: center;">
21
+ <div class="col-xs-4 col-md-2 col-md-offset-6">
22
+ <div class="form-group has-warning">
23
+ <input type="text" class="form-control" ng-model="search" placeholder="search"/>
24
+ </div>
25
+ </div>
26
+ <div class="col-xs-2 col-sm-1 col-sm-offset-6 col-md-offset-2 col-xs-offset-4">
27
+ <a ng-click="playRandom()" class="btn btn-danger btn-fab btn-raised fa fa-random"></a>
28
+ </div>
29
+ <div class="col-xs-2 col-sm-1">
30
+ <a ng-click="refresh()" class="btn btn-danger btn-fab btn-raised fa" ng-class="{ 'fa-sign-in': !loggedIn, 'fa-sign-out': loggedIn}"></a>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ <div class="row" ng-show="!loggedIn">
35
+ <div class="well col-xs-8 col-xs-offset-2 col-sm-6 col-sm-offset-3 col-md-4 col-md-offset-4">
36
+ <form class="form-horizontal">
37
+ <fieldset>
38
+ <!-- <legend>Sign in</legend> -->
39
+ <div class="form-group has-warning">
40
+ <!-- <label for="inputApiKey" class="col-lg-2 control-label">api_key</label> -->
41
+ <div class="col-lg-12">
42
+ <input type="text" class="form-control input-lg" ng-model="json.api_key" id="inputApiKey" placeholder="api_key">
43
+ </div>
44
+ <!-- <label for="inputSecret" class="col-lg-2 control-label">secret</label> -->
45
+ <div class="col-lg-12">
46
+ <input type="password" class="form-control input-lg" ng-model="json.secret" id="inputSecret" placeholder="secret">
47
+ </div>
48
+ </div>
49
+ <div class="form-group">
50
+ <div class="col-lg-10 col-lg-offset-2 text-center">
51
+ <button type="submit" class="btn btn-danger" ng-click="refresh()">login</button>
52
+ </div>
53
+ </div>
54
+ </fieldset>
55
+ </form>
56
+ </div>
57
+ </div>
58
+
59
+ <div class="row" ng-show="loggedIn">
60
+ <div class="col-md-6">
61
+ <div class="embed-responsive embed-responsive-16by9 well">
62
+ <youtube-video class="embed-responsive-item" video-url="ytVideoUrl" player="bestPlayer" player-vars="playerVars"></youtube-video>
63
+ </div>
64
+ </div>
65
+ <div class="col-md-6">
66
+ <div class="list-group well">
67
+ <div ng-repeat="result in results | filter: search">
68
+ <div class="list-group-item vertical-center" ng-class="{ selected: isSelected(result) }">
69
+ <div class="row-action-primary">
70
+ <i class="fa fa-edit" ng-click="select(result)" data-toggle="modal" data-target="#complete-dialog"></i>
71
+ </div>
72
+ <div class="row-content" style="display: flex; align-items: center;">
73
+ <div class="least-content row-action-primary">
74
+ </div>
75
+ <div ng-click="play(result)">
76
+ <h4 class="list-group-item-heading no-wrap">{{ result.name || result}}</h4>
77
+ <p class="disabled list-group-item-text no-wrap disabled">{{ result.url }}</p>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ <div class="list-group-separator"></div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ <div ng-show="loggedIn">
87
+ <a ng-click="newSong()" data-toggle="modal" data-target="#complete-dialog" class="btn btn-danger btn-fab btn-raised fa fa-plus add-button"></a>
88
+ </div>
89
+
90
+ <div id="complete-dialog" class="modal fade" tabindex="-1">
91
+ <div class="modal-dialog modal-sm">
92
+ <div class="modal-content">
93
+ <div class="modal-header">
94
+ <!-- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> -->
95
+ <div class="form-group has-warning">
96
+ </div>
97
+ </div>
98
+ <div class="modal-body">
99
+ <div class="form-group has-warning">
100
+ <input class="form-control" ng-model="selected.name" placeholder="title" />
101
+ <input class="form-control" ng-model="selected.url" placeholder="url" />
102
+ <input class="form-control" ng-model="selected.moods" placeholder="moods" ng-list required>
103
+ </div>
104
+ </div>
105
+ <div class="modal-footer">
106
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
107
+ <button type="button" class="btn btn-danger" ng-click="save(selected)">Save changes</button>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
114
+ <script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
115
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
116
+
117
+ <script src="/bootstrap-material-design/dist/js/ripples.min.js"></script>
118
+ <script src="/bootstrap-material-design/dist/js/material.min.js"></script>
119
+
120
+ <script src="//www.youtube.com/iframe_api"></script>
121
+ <script src="/music/angular-youtube-embed.js"></script>
122
+
123
+ <script src="/javascripts/jnorthpole.js"></script>
124
+ <script src="/music/music.js"></script>
125
+ </body>
126
+ </html>
@@ -0,0 +1,99 @@
1
+ $(() ->
2
+ $.material.init()
3
+ )
4
+
5
+ app = angular.module('app', [ 'youtube-embed' ])
6
+
7
+ app.controller 'MainCtrl', ($scope, youtubeEmbedUtils) ->
8
+
9
+ $scope.refresh = ->
10
+ if $scope.loggedIn
11
+ $scope.loggedIn = false
12
+ $scope.results = []
13
+ localStorage.clear()
14
+ $scope.json =
15
+ 'api_key': 'guest'
16
+ 'secret': 'guest'
17
+ 'category': 'music'
18
+ else
19
+ $scope.loggedIn = false
20
+ jNorthPole.getStorage $scope.json, (data) ->
21
+ if $scope.json.apiKey != 'guest' and !data.error?
22
+ localStorage.json = angular.toJson($scope.json)
23
+ unless data.error? and data.api_key != 'guest'
24
+ $scope.apiKey = $scope.json.apiKey
25
+ $scope.results = data
26
+ $scope.loggedIn = true
27
+ $scope.$apply()
28
+ return
29
+
30
+ return
31
+
32
+ jNorthPole.BASE_URL = 'https://json.northpole.ro/'
33
+ $scope.results = []
34
+ $scope.playerVars =
35
+ controls: 1
36
+ autoplay: 1
37
+ if localStorage.json == undefined
38
+ $scope.json =
39
+ 'api_key': 'guest'
40
+ 'secret': 'guest'
41
+ 'category': 'music'
42
+ else
43
+ $scope.json = angular.fromJson(localStorage.json)
44
+ $scope.refresh()
45
+
46
+ $scope.play = (video) ->
47
+ return unless video?
48
+ if $scope.ytVideoUrl == video.url and $scope.bestPlayer? and $scope.bestPlayer.getPlayerState() == 1
49
+ $scope.bestPlayer.pauseVideo()
50
+ else
51
+ $scope.ytVideoUrl = video.url
52
+ $scope.bestPlayer.playVideo() if $scope.bestPlayer?
53
+ # console.log youtubeEmbedUtils.getIdFromURL($scope.ytVideoUrl)
54
+ return
55
+
56
+ $scope.edit = (video) ->
57
+ $scope.selected = video
58
+ return
59
+
60
+ $scope.save = (video) ->
61
+ $scope.selected.secret = $scope.json.secret
62
+ json = angular.copy($scope.selected)
63
+
64
+ log = (data) ->
65
+ console.log data
66
+ return
67
+
68
+ if video.id?
69
+ jNorthPole.putStorage json, log
70
+ else
71
+ json.api_key = $scope.json.api_key
72
+ json.category = 'music'
73
+ jNorthPole.createStorage json, log
74
+ $('#complete-dialog').modal('hide')
75
+ $scope.selected = undefined
76
+ return
77
+
78
+ $scope.newSong = ->
79
+ $scope.selected = {}
80
+ return
81
+
82
+ $scope.select = (json) ->
83
+ $scope.selected = json
84
+ return
85
+
86
+ $scope.isSelected = (video) ->
87
+ $scope.ytVideoUrl == video.url
88
+
89
+ $scope.playRandom = ->
90
+ item = $scope.results[Math.floor(Math.random() * $scope.results.length)]
91
+ $scope.play item
92
+ if player?
93
+ player.seekTo 0
94
+ return
95
+
96
+ $scope.$on 'youtube.player.ended', ($event, player) ->
97
+ $scope.playRandom player
98
+ return
99
+ return
@@ -0,0 +1,63 @@
1
+ $size: 56px
2
+
3
+ html, body
4
+ height: 100%
5
+
6
+ .bear
7
+ background: url(/images/bear.png) no-repeat center center
8
+
9
+ .embed-responsive
10
+ position: relative
11
+ display: block
12
+ height: 0
13
+ padding: 0
14
+ overflow: hidden
15
+
16
+ .embed-responsive.embed-responsive-16by9
17
+ padding-bottom: 56.25%
18
+
19
+ .embed-responsive-item
20
+ @extend .bear
21
+ position: absolute
22
+ top: 0
23
+ bottom: 0
24
+ left: 0
25
+ width: 100%
26
+ height: 100%
27
+ border: 0
28
+
29
+ .container
30
+ z-index: 10
31
+
32
+ .content-container
33
+ padding-top: $size + 10px
34
+ padding-bottom: $size - 10px
35
+
36
+ .add-button
37
+ margin-bottom: 10px !important
38
+ margin-right: 10px !important
39
+ position: fixed
40
+ right: 0
41
+ bottom: 0
42
+
43
+ .no-wrap
44
+ white-space: nowrap
45
+ overflow: hidden
46
+ text-overflow: ellipsis
47
+
48
+ .vertical-center
49
+ display: flex
50
+ align-items: center
51
+
52
+ p.disabled
53
+ color: #bdbdbd
54
+
55
+ .selected
56
+ background-color: red !important
57
+ p
58
+ color: #f5f5f5
59
+ h4
60
+ color: #f5f5f5 !important
61
+
62
+ .list-group-separator
63
+ background-color: white
@@ -45,3 +45,6 @@ input[type="text"], textarea
45
45
 
46
46
  .form-control:focus
47
47
  border-color: #ccc
48
+
49
+ .realtime-input
50
+ background-color: #f5f5f5
@@ -8,6 +8,8 @@
8
8
  %br
9
9
  %a{href: '#register'} How do I register?
10
10
  %br
11
+ %a{href: '#realtime'} Is there an experimental realtime module?
12
+ %br
11
13
  %h3#what What is json.northpole.ro?
12
14
  %p
13
15
  json.northpole.ro is a JSON cloud storage service.
@@ -36,3 +38,8 @@
36
38
  %a{href: '/playground'} playground
37
39
  \. If you can't do that, there is a good chance you don't understand
38
40
  how the API works. Think of it as a tutorial.
41
+ %h3#realtime Is there an experimental realtime module?
42
+ %p
43
+ Yes. Go to
44
+ %a{href: '/websocket'} /websocket
45
+ to give it a spin.
@@ -41,4 +41,5 @@
41
41
  = js "https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"
42
42
  = js "//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"
43
43
  = js "/javascripts/jnorthpole.js"
44
+ = js "/javascripts/realtime.js"
44
45
  = js "/javascripts/app.js"
@@ -0,0 +1,2 @@
1
+ :javascript
2
+ window.location.href = "/music/index.html";
@@ -0,0 +1,7 @@
1
+ %p
2
+ You are subscribed to the
3
+ %b jNorthPoleChat
4
+ channel. Everybody in this channel will be able to view your messages.
5
+ %input.realtime-input.form-control{ type: 'text', placeholder: 'write your message and press enter' }
6
+ %br
7
+ %pre.realtime-output
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ki::ChannelManager do
4
+ let(:cm) { Ki::ChannelManager }
5
+ let(:db) { Ki::Orm::Db.instance }
6
+
7
+ it 'returns the db connection' do
8
+ expect(cm.db).to eq db
9
+ end
10
+
11
+ it 'stores a socket on connection' do
12
+ expect {
13
+ cm.connect
14
+ }.to change { db.count 'realtime_channel_sockets' }.by(1)
15
+ end
16
+
17
+ it 'removes a channel socket on disconnect' do
18
+ socket = cm.connect
19
+ expect {
20
+ cm.disconnect(socket)
21
+ }.to change { db.count 'realtime_channel_sockets' }.by(-1)
22
+ end
23
+
24
+ it 'removes all the channel subscriptions on disconnect' do
25
+ socket = cm.connect
26
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
27
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel-2')
28
+ expect {
29
+ cm.disconnect(socket)
30
+ }.to change { db.count 'realtime_channel_subscriptions' }.by(-2)
31
+ end
32
+
33
+ it 'returns the list of channel sockets' do
34
+ cm.cleanup
35
+ expect(cm.sockets).to be_empty
36
+
37
+ cm.connect
38
+ expect(cm.sockets).to_not be_empty
39
+ end
40
+
41
+ it 'subscribes to a channel' do
42
+ socket = cm.connect
43
+
44
+ expect {
45
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
46
+ }.to change { db.count 'realtime_channel_subscriptions' }.by(1)
47
+ end
48
+
49
+ it 'does not subscribe to the same channel twice' do
50
+ socket = cm.connect
51
+
52
+ expect {
53
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
54
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
55
+ }.to change { db.count 'realtime_channel_subscriptions' }.by(1)
56
+ end
57
+
58
+ it 'unsubscribes fom a channel' do
59
+ socket = cm.connect
60
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
61
+
62
+ expect {
63
+ cm.unsubscribe(socket_id: socket['id'], 'type' => 'unsubscribe', 'channel_name' => 'test-channel')
64
+ }.to change { db.count 'realtime_channel_subscriptions' }.by(-1)
65
+ end
66
+
67
+ it 'returns messages from a tick'
68
+
69
+ it 'publishes a message' do
70
+ expect {
71
+ item = cm.publish(hello: 'world')
72
+ expect(item['created_at']).to_not be_nil # TODO: might need present?
73
+ }.to change { db.count 'realtime_channel_messages' }.by(1)
74
+ end
75
+
76
+ it 'cleans up the data' do
77
+ cm.cleanup
78
+ expect(db.count('realtime_channel_sockets')).to eq 0
79
+ expect(db.count('realtime_channel_subscriptions')).to eq 0
80
+ expect(db.count('realtime_channel_messages')).to eq 0
81
+ end
82
+ end