├── .devcontainer
├── Dockerfile
└── devcontainer.json
├── .github
└── workflows
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── bootstrap
├── console
├── setup
├── test
└── update
├── images
├── social.png
└── social2.png
├── lamby.gemspec
├── lib
├── lamby.rb
└── lamby
│ ├── cold_start_metrics.rb
│ ├── config.rb
│ ├── debug.rb
│ ├── handler.rb
│ ├── logger.rb
│ ├── proxy_context.rb
│ ├── proxy_server.rb
│ ├── rack.rb
│ ├── rack_alb.rb
│ ├── rack_http.rb
│ ├── rack_rest.rb
│ ├── railtie.rb
│ ├── ssm_parameter_store.rb
│ ├── tasks.rake
│ └── version.rb
└── test
├── cold_start_metrics_test.rb
├── debug_test.rb
├── dummy_app
├── Rakefile
├── app
│ ├── assets
│ │ └── config
│ │ │ └── manifest.js
│ ├── controllers
│ │ ├── .keep
│ │ └── application_controller.rb
│ ├── images
│ │ └── 1.png
│ ├── models
│ │ └── dummy
│ │ │ └── .keep
│ └── views
│ │ ├── application
│ │ ├── index.html.erb
│ │ └── percent.html.erb
│ │ └── layouts
│ │ └── application.html.erb
├── bin
│ ├── rails
│ └── rake
├── config.ru
├── config
│ ├── boot.rb
│ ├── initializers
│ │ ├── .keep
│ │ └── secret_token.rb
│ └── routes.rb
├── init.rb
└── public
│ ├── .keep
│ ├── 1-public.png
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ ├── favicon.ico
│ └── robots.txt
├── handler_test.rb
├── lamby_core_test.rb
├── proxy_context_test.rb
├── proxy_server_test.rb
├── rack_alb_test.rb
├── rack_deflate_test.rb
├── rack_http_test.rb
├── rack_rest_test.rb
├── ssm_parameter_store_test.rb
├── test_helper.rb
└── test_helper
├── dummy_app_helpers.rb
├── event_helpers.rb
├── events
├── alb.rb
├── base.rb
├── http_v1.rb
├── http_v1_post.rb
├── http_v2.rb
├── http_v2_post.rb
├── rest.rb
└── rest_post.rb
├── jobs_helpers.rb
├── lambda_context.rb
├── lambdakiq_helpers.rb
└── stream_helpers.rb
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/devcontainers/ruby:3.1
2 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lamby",
3 | "build": {
4 | "dockerfile": "Dockerfile"
5 | },
6 | "features": {
7 | "ghcr.io/devcontainers/features/node:latest": {},
8 | "ghcr.io/devcontainers/features/docker-in-docker:latest": {},
9 | "ghcr.io/devcontainers/features/sshd:latest": {}
10 | },
11 | "customizations": {
12 | "vscode": {
13 | "settings": {
14 | "editor.formatOnSave": true
15 | },
16 | "extensions": []
17 | }
18 | },
19 | "remoteUser": "vscode"
20 | }
21 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Gem
2 | on:
3 | workflow_dispatch:
4 | branches:
5 | - master
6 |
7 | jobs:
8 | push:
9 | name: Push gem to RubyGems.org
10 | runs-on: ubuntu-latest
11 |
12 | permissions:
13 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
14 | contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag
15 |
16 | steps:
17 | # Set up
18 | - uses: actions/checkout@v4
19 | - name: Set up Ruby
20 | uses: ruby/setup-ruby@v1
21 | with:
22 | bundler-cache: true
23 | ruby-version: ruby
24 |
25 | # Release
26 | - uses: rubygems/release-gem@v1
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: CI/CD
2 | on: [push]
3 | jobs:
4 | image:
5 | name: Image
6 | runs-on: ubuntu-20.04
7 | steps:
8 | - uses: actions/checkout@v3
9 | - uses: docker/login-action@v2
10 | with:
11 | registry: ghcr.io
12 | username: ${{ github.repository_owner }}
13 | password: ${{ secrets.GITHUB_TOKEN }}
14 | - uses: devcontainers/ci@v0.3
15 | with:
16 | push: always
17 | imageName: ghcr.io/rails-lambda/lamby-devcontainer
18 | cacheFrom: ghcr.io/rails-lambda/lamby-devcontainer
19 | runCmd: echo DONE!
20 | test:
21 | runs-on: ubuntu-20.04
22 | needs: image
23 | steps:
24 | - name: Checkout
25 | uses: actions/checkout@v3
26 | - name: Setup & Test
27 | uses: devcontainers/ci@v0.3
28 | with:
29 | push: never
30 | cacheFrom: ghcr.io/rails-lambda/lamby-devcontainer
31 | env: |
32 | CI
33 | runCmd: |
34 | ./bin/setup
35 | ./bin/test
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /pkg/
6 | /spec/reports/
7 | /tmp/
8 | /vendor/bundle
9 | test/dummy_app/.gitignore
10 | test/dummy_app/app.rb
11 | test/dummy_app/template.yaml
12 | test/dummy_app/bin/build
13 | test/dummy_app/bin/deploy
14 | test/dummy_app/tmp
15 | test/dummy_app/log/**/*
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Keep A Changelog!
2 |
3 | See this http://keepachangelog.com link for information on how we want this documented formatted.
4 |
5 | ## v6.0.0
6 |
7 | - Remove "cookies" header from rack response to conform to Lambda proxy integration requirements.
8 |
9 | ## v6.0.0
10 |
11 | ### Changed
12 |
13 | - ⚠️ Breaking Changes ⚠️
14 | - Remove Rack v2 support.
15 | - Added Rack v3 support.
16 |
17 | ## v5.2.1
18 |
19 | - Rack 3.X compatibility, by removing uninitialized constants.
20 | - Exit gracefully when SIGTERM or SIGINT signal occurs.
21 |
22 | ## v5.2.0
23 |
24 | ### Fixed
25 |
26 | - Safely Pass Percent Symbols in Paths Fixes #170
27 |
28 | ## v5.1.0
29 |
30 | ### Added
31 |
32 | - New CloudWatch cold start metrics. Defaults to off. Enable with `config.cold_start_metrics = true`.
33 |
34 | ## v5.0.0
35 |
36 | ### Changed
37 |
38 | - ⚠️ Breaking Changes ⚠️
39 | - Remove Lamby::Runner & Lamby::Command in favor of LambdaConsole Ruby gem.
40 | - New runner pattern exceptions from above. Now: LambdaConsole::Run::UnknownCommandPattern
41 | - Switch your own runner events to new https://github.com/rails-lambda/lambda-console spec.
42 |
43 | ## v4.3.1, v4.3.2
44 |
45 | ### Changed
46 |
47 | - Command response will be #inspect for every command.
48 | - Added x-lambda-console option to the event.
49 |
50 | ## v4.3.0
51 |
52 | ### Changed
53 |
54 | - Default Lamby::Runner::PATTERNS to allow everything.
55 |
56 | ### Added
57 |
58 | - New Lamby::Command for IRB style top level binding evals.
59 |
60 | ## v4.2.1
61 |
62 | ### Added
63 |
64 | - Local Development Proxy Server with Puma. See #164
65 |
66 | ## v4.2.0
67 |
68 | ### Added
69 |
70 | - Local Development Proxy Server. See #164
71 |
72 | ## v4.1.1
73 |
74 | ### Changed
75 |
76 | - New lamby.cloud site and GitHub community organization.
77 |
78 | ## v4.1.0
79 |
80 | ### Added
81 |
82 | - Future-ready LambdaCable.cmd handler detection.
83 |
84 | ## v4.0.2
85 |
86 | ### Fixed
87 |
88 | - Runner's Open3 uses crypteia friendly env.
89 |
90 | ## v4.0.1
91 |
92 | ### Added
93 |
94 | - New `Lamby.config.handled_proc` called with ensure via `Lamby.cmd`
95 |
96 | ## v4.0.0
97 |
98 | ### Added
99 |
100 | - New `Lamby.config.rack_app` with default Rack builder.
101 | - The `Lamby.cmd` to simplify `CMD` with the new config.app from above.
102 |
103 | #### Removed
104 |
105 | - All lamby installer templates.
106 | - Remove SAM env checks used during debug mode.
107 | - Removed showing environment variables in debug mode.
108 | - Need to `require: false` when adding the Lamby gem to your `Gemfile`.
109 | - Dotenv integration. Use [Crypteia](https://github.com/customink/crypteia) now.
110 |
111 | #### Changed
112 |
113 | - Tested Rack 3.x.
114 |
115 | ## v3.1.3
116 |
117 | #### Fixed
118 |
119 | - The ::Rack::Utils namespace. Fixes #123.
120 |
121 | ## v3.1.2
122 |
123 | #### Fixed
124 |
125 | - Lambdakiq Handler Integration. Fixes #120.
126 |
127 | ## v3.1.1
128 |
129 | #### Fixed
130 |
131 | - X-Request-Start header value for New Relic with API Gateway.
132 |
133 | ## v3.1.0
134 |
135 | #### Added
136 |
137 | - Add X-Request-Start header for New Relic with API Gateway.
138 |
139 | ## v3.0.3
140 |
141 | #### Fixed
142 |
143 | - Ruby 2.7 Warnings | Logger. Thanks @jessedoyle
144 |
145 | ## v3.0.2
146 |
147 | #### Added
148 |
149 | - Runner now returns STDOUT/STDERR as body.
150 |
151 | ## v3.0.1
152 |
153 | #### Fixed
154 |
155 | - Fix Lambdakiq integration. Thanks #97.
156 |
157 | ## v3.0.0
158 |
159 | #### Added
160 |
161 | - Automatically handle `Lambdakiq.jobs?(event)`.
162 | - New event for tasks like DB migrations. #80 #93
163 |
164 | #### Changed
165 |
166 | - Updated template files to latest lambda container standards.
167 |
168 | ## v2.8.0
169 |
170 | #### Fixed
171 |
172 | - Perform rack body closing hooks on request #85
173 |
174 | ## v2.7.1
175 |
176 | #### Removed
177 |
178 | - Bootsnap setup convenience require.
179 |
180 | ## v2.7.0
181 |
182 | #### Added
183 |
184 | - Support EventBridge events in handler with default proc to log.
185 |
186 | ## v2.6.3
187 |
188 | #### Added
189 |
190 | - Bootsnap setup convenience require.
191 |
192 | ## v2.6.2
193 |
194 | - Fixed Rack::Deflate usage with an ALB.
195 |
196 | ## v2.6.1
197 |
198 | #### Fixed
199 |
200 | - Support redirects with empty response body.
201 |
202 | #### Added
203 |
204 | - Tests for enabling Rack::Deflate middleware by passing RACK_DEFLATE_ENABLED env variable.
205 |
206 | ## v2.6.0
207 |
208 | #### Fixed
209 |
210 | - Support multiple Set-Cookie headers for all rest types.
211 |
212 | ## v2.5.3
213 |
214 | #### Fixed
215 |
216 | - Base64 encode response body if the rack response is gzip or brotli compressed.
217 |
218 | ## v2.5.2
219 |
220 | - SSM file always overwrites. Fixes #65.
221 |
222 | ## v2.5.1
223 |
224 | #### Fixed
225 |
226 | - Quoting in describe-subnets #62 Thanks @atwoodjw
227 |
228 | ## v2.5.0
229 |
230 | #### Changed
231 |
232 | - Install files to favor containers.
233 |
234 | ## v2.2.2
235 |
236 | #### Changed
237 |
238 | - More ActiveSupport removal. Better ENV.to_h.
239 |
240 | ## v2.2.1
241 |
242 | #### Changed
243 |
244 | - More ActiveSupport removal from SsmParameterStore.
245 |
246 | ## v2.2.0
247 |
248 | #### Changed
249 |
250 | - Remove dependency on `activesupport` for rack-only applications.
251 | - Remove ActiveSupport artifacts:
252 | - Replace `strip_heredoc` with `<<~HEREDOC`.
253 | - Remove instances of `Object#try`, replace with `&.`.
254 | - Use `Rack::Utils.build_nested_query` in place of `Object#to_query`.
255 | - Replace `Object#present?` with `to_s.empty?`.
256 | - Replace `Array.wrap` with `Array[obj].compact.flatten`.
257 | - Add a check against the `RAILS_ENV` AND `RACK_ENV` environment
258 | variables prior to enabling debug mode.
259 |
260 | ## v2.1.0
261 |
262 | #### Changed
263 |
264 | - Only load the railtie if `Rails` is defined.
265 |
266 | ## v2.0.1
267 |
268 | #### Changed
269 |
270 | - Remove Rails runtime dep. Only rack is needed.
271 |
272 | ## v2.0.0
273 |
274 | Support for new API Gateway HTTP APIs!!!
275 |
276 | #### Changed
277 |
278 | - The `Lamby.handler` must have a `:rack` option. One of `:http`, `:rest`, `:alb`.
279 | - Renamed template generators to match options above.
280 | - The `lamby:install` task now defaults to HTTP API.
281 | - Changed the name of `:api` rack option to `:rest`.
282 | - Removed `export` from Dotenv files. Better Docker compatability.
283 |
284 | #### Added
285 |
286 | - New rack handler for HTTP API v1 and v2.
287 | - Lots of backfill tests for, ALBs & REST APIs.
288 |
289 | ## v1.0.3
290 |
291 | #### Changed
292 |
293 | - Change shebangs to `#!/usr/bin/env bash`
294 |
295 | ## v1.0.2
296 |
297 | #### Changed
298 |
299 | - Adds an optional 'overwrite' parameter to #to_env.
300 |
301 | ## v1.0.1
302 |
303 | #### Changed
304 |
305 | - Links in bin/build templates to point to lamby.custominktech.com site.
306 |
307 | ## v1.0.0
308 |
309 | #### Fixed
310 |
311 | - ALB query params & binary responses. Fixes #38.
312 |
313 | ## v0.6.0
314 |
315 | #### Added
316 |
317 | - APPLICATION LOAD BALANACER SUPPORT!!! The new default. Use `rack: :api` option to handler for API Gateway support.
318 |
319 | #### Changed
320 |
321 | - Rake task `lamby:install` now defaults to `application_load_balancer`
322 |
323 | ## v0.5.1
324 |
325 | #### Fixed
326 |
327 | - The .gitignore file template. Fix .aws-sam dir.
328 |
329 | ## v0.5.0
330 |
331 | #### Added
332 |
333 | - Template generators for first install. Ex: `./bin/rake -r lamby lamby:install:api_gateway`.
334 | - New `Lamby::SsmParameterStore.get!` helper.
335 |
336 | ## v0.4.1
337 |
338 | #### Fixed
339 |
340 | - Fix type in v0.4.0 fix below.
341 |
342 | ## v0.4.0
343 |
344 | #### Fixed
345 |
346 | - File uploads in #33 using `CONTENT_TYPE` and `CONTENT_LENGTH`.
347 |
348 | ## v0.3.2
349 |
350 | #### Added
351 |
352 | - Pass Request ID for CloudWatch logs. Fixes #30.
353 |
354 | ## v0.3.1
355 |
356 | #### Changed
357 |
358 | - Docs and SAM template tweaks.
359 |
360 | ## v0.3.0
361 |
362 | #### Added
363 |
364 | - Secure configs rake task.
365 | - Project bin setup and tests.
366 |
367 | #### Changed
368 |
369 | - SAM template tweaks.
370 |
371 | ## v0.2.0
372 |
373 | #### Changed
374 |
375 | - Simple docs and project re-organization.
376 |
377 | ## v0.1.0
378 |
379 | #### Added
380 |
381 | - New gem and placeholder in rubygems.
382 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at ken@metaskills.net. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3 | gemspec
4 |
5 | gem 'rails', '7.1.3.4'
6 | gem 'mocha', '~> 2.4'
7 | gem 'rack', "~> 3.1", ">= 3.1.7"
8 |
9 | group :test do
10 | gem 'lambdakiq'
11 | end
12 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | lamby (6.0.1)
5 | lambda-console-ruby
6 | rack (>= 3.0.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actioncable (7.1.3.4)
12 | actionpack (= 7.1.3.4)
13 | activesupport (= 7.1.3.4)
14 | nio4r (~> 2.0)
15 | websocket-driver (>= 0.6.1)
16 | zeitwerk (~> 2.6)
17 | actionmailbox (7.1.3.4)
18 | actionpack (= 7.1.3.4)
19 | activejob (= 7.1.3.4)
20 | activerecord (= 7.1.3.4)
21 | activestorage (= 7.1.3.4)
22 | activesupport (= 7.1.3.4)
23 | mail (>= 2.7.1)
24 | net-imap
25 | net-pop
26 | net-smtp
27 | actionmailer (7.1.3.4)
28 | actionpack (= 7.1.3.4)
29 | actionview (= 7.1.3.4)
30 | activejob (= 7.1.3.4)
31 | activesupport (= 7.1.3.4)
32 | mail (~> 2.5, >= 2.5.4)
33 | net-imap
34 | net-pop
35 | net-smtp
36 | rails-dom-testing (~> 2.2)
37 | actionpack (7.1.3.4)
38 | actionview (= 7.1.3.4)
39 | activesupport (= 7.1.3.4)
40 | nokogiri (>= 1.8.5)
41 | racc
42 | rack (>= 2.2.4)
43 | rack-session (>= 1.0.1)
44 | rack-test (>= 0.6.3)
45 | rails-dom-testing (~> 2.2)
46 | rails-html-sanitizer (~> 1.6)
47 | actiontext (7.1.3.4)
48 | actionpack (= 7.1.3.4)
49 | activerecord (= 7.1.3.4)
50 | activestorage (= 7.1.3.4)
51 | activesupport (= 7.1.3.4)
52 | globalid (>= 0.6.0)
53 | nokogiri (>= 1.8.5)
54 | actionview (7.1.3.4)
55 | activesupport (= 7.1.3.4)
56 | builder (~> 3.1)
57 | erubi (~> 1.11)
58 | rails-dom-testing (~> 2.2)
59 | rails-html-sanitizer (~> 1.6)
60 | activejob (7.1.3.4)
61 | activesupport (= 7.1.3.4)
62 | globalid (>= 0.3.6)
63 | activemodel (7.1.3.4)
64 | activesupport (= 7.1.3.4)
65 | activerecord (7.1.3.4)
66 | activemodel (= 7.1.3.4)
67 | activesupport (= 7.1.3.4)
68 | timeout (>= 0.4.0)
69 | activestorage (7.1.3.4)
70 | actionpack (= 7.1.3.4)
71 | activejob (= 7.1.3.4)
72 | activerecord (= 7.1.3.4)
73 | activesupport (= 7.1.3.4)
74 | marcel (~> 1.0)
75 | activesupport (7.1.3.4)
76 | base64
77 | bigdecimal
78 | concurrent-ruby (~> 1.0, >= 1.0.2)
79 | connection_pool (>= 2.2.5)
80 | drb
81 | i18n (>= 1.6, < 2)
82 | minitest (>= 5.1)
83 | mutex_m
84 | tzinfo (~> 2.0)
85 | aws-eventstream (1.3.0)
86 | aws-partitions (1.956.0)
87 | aws-sdk-core (3.201.1)
88 | aws-eventstream (~> 1, >= 1.3.0)
89 | aws-partitions (~> 1, >= 1.651.0)
90 | aws-sigv4 (~> 1.8)
91 | jmespath (~> 1, >= 1.6.1)
92 | aws-sdk-sqs (1.80.0)
93 | aws-sdk-core (~> 3, >= 3.201.0)
94 | aws-sigv4 (~> 1.5)
95 | aws-sdk-ssm (1.150.0)
96 | aws-sdk-core (~> 3, >= 3.165.0)
97 | aws-sigv4 (~> 1.1)
98 | aws-sigv4 (1.8.0)
99 | aws-eventstream (~> 1, >= 1.0.2)
100 | base64 (0.2.0)
101 | bigdecimal (3.1.8)
102 | builder (3.3.0)
103 | coderay (1.1.3)
104 | concurrent-ruby (1.3.3)
105 | connection_pool (2.4.1)
106 | crass (1.0.6)
107 | date (3.3.4)
108 | drb (2.2.1)
109 | erubi (1.13.0)
110 | globalid (1.2.1)
111 | activesupport (>= 6.1)
112 | i18n (1.14.5)
113 | concurrent-ruby (~> 1.0)
114 | io-console (0.7.2)
115 | irb (1.14.0)
116 | rdoc (>= 4.0.0)
117 | reline (>= 0.4.2)
118 | jmespath (1.6.2)
119 | lambda-console-ruby (1.0.0)
120 | lambdakiq (2.2.0)
121 | activejob
122 | aws-sdk-sqs
123 | concurrent-ruby
124 | railties
125 | loofah (2.22.0)
126 | crass (~> 1.0.2)
127 | nokogiri (>= 1.12.0)
128 | mail (2.8.1)
129 | mini_mime (>= 0.1.1)
130 | net-imap
131 | net-pop
132 | net-smtp
133 | marcel (1.0.4)
134 | method_source (1.0.0)
135 | mini_mime (1.1.5)
136 | minitest (5.24.1)
137 | minitest-focus (1.3.1)
138 | minitest (>= 4, < 6)
139 | mocha (2.4.0)
140 | ruby2_keywords (>= 0.0.5)
141 | mutex_m (0.2.0)
142 | net-imap (0.4.14)
143 | date
144 | net-protocol
145 | net-pop (0.1.2)
146 | net-protocol
147 | net-protocol (0.2.2)
148 | timeout
149 | net-smtp (0.5.0)
150 | net-protocol
151 | nio4r (2.7.3)
152 | nokogiri (1.16.6-aarch64-linux)
153 | racc (~> 1.4)
154 | nokogiri (1.16.6-arm64-darwin)
155 | racc (~> 1.4)
156 | nokogiri (1.16.6-x86_64-linux)
157 | racc (~> 1.4)
158 | pry (0.14.2)
159 | coderay (~> 1.1)
160 | method_source (~> 1.0)
161 | psych (5.1.2)
162 | stringio
163 | racc (1.8.0)
164 | rack (3.1.7)
165 | rack-session (2.0.0)
166 | rack (>= 3.0.0)
167 | rack-test (2.1.0)
168 | rack (>= 1.3)
169 | rackup (2.1.0)
170 | rack (>= 3)
171 | webrick (~> 1.8)
172 | rails (7.1.3.4)
173 | actioncable (= 7.1.3.4)
174 | actionmailbox (= 7.1.3.4)
175 | actionmailer (= 7.1.3.4)
176 | actionpack (= 7.1.3.4)
177 | actiontext (= 7.1.3.4)
178 | actionview (= 7.1.3.4)
179 | activejob (= 7.1.3.4)
180 | activemodel (= 7.1.3.4)
181 | activerecord (= 7.1.3.4)
182 | activestorage (= 7.1.3.4)
183 | activesupport (= 7.1.3.4)
184 | bundler (>= 1.15.0)
185 | railties (= 7.1.3.4)
186 | rails-dom-testing (2.2.0)
187 | activesupport (>= 5.0.0)
188 | minitest
189 | nokogiri (>= 1.6)
190 | rails-html-sanitizer (1.6.0)
191 | loofah (~> 2.21)
192 | nokogiri (~> 1.14)
193 | railties (7.1.3.4)
194 | actionpack (= 7.1.3.4)
195 | activesupport (= 7.1.3.4)
196 | irb
197 | rackup (>= 1.0.0)
198 | rake (>= 12.2)
199 | thor (~> 1.0, >= 1.2.2)
200 | zeitwerk (~> 2.6)
201 | rake (13.2.1)
202 | rdoc (6.7.0)
203 | psych (>= 4.0.0)
204 | reline (0.5.9)
205 | io-console (~> 0.5)
206 | ruby2_keywords (0.0.5)
207 | stringio (3.1.1)
208 | thor (1.3.1)
209 | timecop (0.9.6)
210 | timeout (0.4.1)
211 | tzinfo (2.0.6)
212 | concurrent-ruby (~> 1.0)
213 | webrick (1.8.1)
214 | websocket-driver (0.7.6)
215 | websocket-extensions (>= 0.1.0)
216 | websocket-extensions (0.1.5)
217 | zeitwerk (2.6.16)
218 |
219 | PLATFORMS
220 | aarch64-linux
221 | arm64-darwin-21
222 | arm64-darwin-22
223 | arm64-darwin-23
224 | x86_64-linux
225 |
226 | DEPENDENCIES
227 | aws-sdk-ssm
228 | bundler
229 | lambdakiq
230 | lamby!
231 | minitest
232 | minitest-focus
233 | mocha (~> 2.4)
234 | pry
235 | rack (~> 3.1, >= 3.1.7)
236 | rails (= 7.1.3.4)
237 | rake
238 | timecop
239 | webrick
240 |
241 | BUNDLED WITH
242 | 2.3.26
243 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Ken Collins
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lamby [](https://github.com/rails-lambda/lamby/actions)
2 |
3 |
Simple Rails & AWS Lambda Integration using Rack
4 |
5 |
6 | Lamby is an [AWS Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter) for Rack applications.
7 |
8 | We support Lambda [Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html), [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) (HTTP or REST, all payload versions), and even [Application Load Balancer](https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html) integrations.
9 |
10 | ## Quick Start
11 |
12 | https://lamby.cloud/docs/quick-start
13 |
14 | ## Full Documentation
15 |
16 | https://lamby.cloud/docs/anatomy
17 |
18 | ## Contributing
19 |
20 | [](https://codespaces.new/rails-lambda/lamby)
21 |
22 | This project is built for [GitHub Codespaces](https://github.com/features/codespaces) using the [Development Container](https://containers.dev) specification. Once you have the repo cloned and setup with a dev container using either Codespaces or [VS Code](#using-vs-code), run the following commands. This will install packages and run tests.
23 |
24 | ```shell
25 | $ ./bin/setup
26 | $ ./bin/test
27 | ```
28 |
29 | #### Using VS Code
30 |
31 | If you have the [Visual Studio Code Dev Container](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension installed you can easily clone this repo locally, use the "Open Folder in Container..." command. This allows you to use the integrated terminal for the commands above.
32 |
33 | ## Code of Conduct
34 |
35 | Everyone interacting in the Lamby project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/rails-lambda/lamby/blob/master/CODE_OF_CONDUCT.md).
36 |
37 | Bug reports and pull requests are welcome on GitHub at https://github.com/rails-lambda/lamby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
38 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | ENV['RUBYOPT'] = '-W:no-deprecated -W:no-experimental'
2 | require "bundler/gem_tasks"
3 | require "rake/testtask"
4 |
5 | Rake::TestTask.new(:test) do |t|
6 | t.libs << "test"
7 | t.libs << "lib"
8 | t.test_files = begin
9 | if ENV['TEST_FILE']
10 | [ENV['TEST_FILE']]
11 | else
12 | FileList["test/**/*_test.rb"]
13 | end
14 | end
15 | t.verbose = false
16 | t.warning = false
17 | end
18 |
19 | Rake::TestTask.new(:test_deflate) do |t|
20 | t.libs << "test"
21 | t.libs << "lib"
22 | t.test_files = FileList["test/rack_deflate_test.rb"]
23 | t.verbose = false
24 | t.warning = false
25 | end
26 |
27 | task :default => [:test, :test_deflate]
28 |
--------------------------------------------------------------------------------
/bin/bootstrap:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "lamby"
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require "irb"
14 | IRB.start(__FILE__)
15 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | echo '== Installing dependencies =='
5 | bundle config set --local path 'vendor/bundle'
6 | bundle install
--------------------------------------------------------------------------------
/bin/test:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | export RAILS_ENV="test"
5 |
6 | bundle exec rake test
7 | LAMBY_TEST_DYNAMIC_HANDLER=1 bundle exec rake test
8 | LAMBY_RACK_DEFLATE_ENABLED=1 bundle exec rake test_deflate
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | rm -rf ./vendor/bundle
5 | ./bin/setup
6 |
--------------------------------------------------------------------------------
/images/social.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/images/social.png
--------------------------------------------------------------------------------
/images/social2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/images/social2.png
--------------------------------------------------------------------------------
/lamby.gemspec:
--------------------------------------------------------------------------------
1 |
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'lamby/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "lamby"
8 | spec.version = Lamby::VERSION
9 | spec.authors = ["Ken Collins"]
10 | spec.email = ["ken@metaskills.net"]
11 | spec.summary = %q{Simple Rails & AWS Lambda Integration using Rack}
12 | spec.description = %q{Simple Rails & AWS Lambda Integration using Rack and various utilities.}
13 | spec.homepage = "https://github.com/rails-lambda/lamby"
14 | spec.license = "MIT"
15 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
16 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17 | end
18 | spec.bindir = "exe"
19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20 | spec.require_paths = ["lib"]
21 | spec.add_dependency 'rack', '>= 3.0.0'
22 | spec.add_dependency 'lambda-console-ruby'
23 | spec.add_development_dependency 'aws-sdk-ssm'
24 | spec.add_development_dependency 'bundler'
25 | spec.add_development_dependency 'rake'
26 | spec.add_development_dependency 'minitest'
27 | spec.add_development_dependency 'minitest-focus'
28 | spec.add_development_dependency 'mocha'
29 | spec.add_development_dependency 'pry'
30 | spec.add_development_dependency 'timecop'
31 | spec.add_development_dependency 'webrick'
32 | end
33 |
--------------------------------------------------------------------------------
/lib/lamby.rb:
--------------------------------------------------------------------------------
1 | require 'lamby/logger'
2 | require 'rack'
3 | require 'base64'
4 | require 'lambda-console-ruby'
5 | require 'lamby/version'
6 | require 'lamby/config'
7 | require 'lamby/rack'
8 | require 'lamby/rack_alb'
9 | require 'lamby/rack_rest'
10 | require 'lamby/rack_http'
11 | require 'lamby/debug'
12 | require 'lamby/cold_start_metrics'
13 | require 'lamby/handler'
14 |
15 | if defined?(Rails)
16 | require 'rails/railtie'
17 | require 'lamby/railtie'
18 | end
19 |
20 | module Lamby
21 |
22 | extend self
23 |
24 | def cmd(event:, context:)
25 | handler(config.rack_app, event, context)
26 | ensure
27 | config.handled_proc.call(event, context)
28 | end
29 |
30 | def handler(app, event, context, options = {})
31 | Handler.call(app, event, context, options)
32 | end
33 |
34 | def config
35 | Lamby::Config.config
36 | end
37 |
38 | autoload :SsmParameterStore, 'lamby/ssm_parameter_store'
39 | autoload :ProxyContext, 'lamby/proxy_context'
40 | autoload :ProxyServer, 'lamby/proxy_server'
41 |
42 | end
43 |
44 | # Add signal traps for clean exit
45 | Signal.trap("TERM") do
46 | puts "Received SIGTERM, exiting gracefully..."
47 | exit!(0) # exit! ensures no exception is raised
48 | end
49 |
50 | Signal.trap("INT") do
51 | puts "Received SIGINT, exiting gracefully..."
52 | exit!(0) # exit! ensures no exception is raised
53 | end
--------------------------------------------------------------------------------
/lib/lamby/cold_start_metrics.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | class ColdStartMetrics
3 | NAMESPACE = 'Lamby'
4 |
5 | @cold_start = true
6 | @cold_start_time = (Time.now.to_f * 1000).to_i
7 |
8 | class << self
9 |
10 | def instrument!
11 | return unless @cold_start
12 | @cold_start = false
13 | now = (Time.now.to_f * 1000).to_i
14 | proactive_init = (now - @cold_start_time) > 10_000
15 | new(proactive_init).instrument!
16 | end
17 |
18 | def clear!
19 | @cold_start = true
20 | @cold_start_time = (Time.now.to_f * 1000).to_i
21 | end
22 |
23 | end
24 |
25 | def initialize(proactive_init)
26 | @proactive_init = proactive_init
27 | @metrics = []
28 | @properties = {}
29 | end
30 |
31 | def instrument!
32 | name = @proactive_init ? 'ProactiveInit' : 'ColdStart'
33 | put_metric name, 1, 'Count'
34 | puts JSON.dump(message)
35 | end
36 |
37 | private
38 |
39 | def dimensions
40 | [{ AppName: rails_app_name }]
41 | end
42 |
43 | def put_metric(name, value, unit = nil)
44 | @metrics << { 'Name': name }.tap do |m|
45 | m['Unit'] = unit if unit
46 | end
47 | set_property name, value
48 | end
49 |
50 | def set_property(name, value)
51 | @properties[name] = value
52 | self
53 | end
54 |
55 | def message
56 | {
57 | '_aws': {
58 | 'Timestamp': timestamp,
59 | 'CloudWatchMetrics': [
60 | {
61 | 'Namespace': NAMESPACE,
62 | 'Dimensions': [dimensions.map(&:keys).flatten],
63 | 'Metrics': @metrics
64 | }
65 | ]
66 | }
67 | }.tap do |m|
68 | dimensions.each { |d| m.merge!(d) }
69 | m.merge!(@properties)
70 | end
71 | end
72 |
73 | def timestamp
74 | Time.current.strftime('%s%3N').to_i
75 | end
76 |
77 | def rails_app_name
78 | Lamby.config.metrics_app_name ||
79 | Rails.application.class.name.split('::').first
80 | end
81 |
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/lib/lamby/config.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | module Config
3 |
4 | def configure
5 | yield(config)
6 | config
7 | end
8 |
9 | def reconfigure
10 | config.reconfigure { |c| yield(c) if block_given? }
11 | end
12 |
13 | def config
14 | @config ||= Configuration.new
15 | end
16 |
17 | extend self
18 |
19 | end
20 |
21 | class Configuration
22 |
23 | def initialize
24 | initialize_defaults
25 | end
26 |
27 | def reconfigure
28 | instance_variables.each { |var| instance_variable_set var, nil }
29 | initialize_defaults
30 | yield(self) if block_given?
31 | self
32 | end
33 |
34 | def rack_app
35 | @rack_app ||= ::Rack::Builder.new { run ::Rails.application }.to_app
36 | end
37 |
38 | def rack_app=(app)
39 | @rack_app = app
40 | end
41 |
42 | def initialize_defaults
43 | @rack_app = nil
44 | @cold_start_metrics = false
45 | @metrics_app_name = nil
46 | @event_bridge_handler = lambda { |event, context| puts(event) }
47 | end
48 |
49 | def event_bridge_handler
50 | @event_bridge_handler
51 | end
52 |
53 | def event_bridge_handler=(func)
54 | @event_bridge_handler = func
55 | end
56 |
57 | def runner_patterns
58 | LambdaConsole::Run::PATTERNS
59 | end
60 |
61 | def handled_proc
62 | @handled_proc ||= Proc.new { |_event, _context| }
63 | end
64 |
65 | def handled_proc=(proc)
66 | @handled_proc = proc
67 | end
68 |
69 | def cold_start_metrics?
70 | @cold_start_metrics
71 | end
72 |
73 | def cold_start_metrics=(bool)
74 | @cold_start_metrics = bool
75 | end
76 |
77 | def metrics_app_name
78 | @metrics_app_name
79 | end
80 |
81 | def metrics_app_name=(name)
82 | @metrics_app_name = name
83 | end
84 |
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/lib/lamby/debug.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | module Debug
3 | extend self
4 |
5 | def on?(event)
6 | params = event['multiValueQueryStringParameters'] || event['queryStringParameters']
7 | (development? || ENV['LAMBY_DEBUG']) && params && params['debug'] == '1'
8 | end
9 |
10 | def call(event, context, env)
11 | [ 200, { 'Content-Type' => 'text/html' }, [body(event, context, env)] ]
12 | end
13 |
14 | private
15 |
16 | def body(event, context, env)
17 | <<-HTML
18 |
19 |
20 |
21 | Lamby Debug Response
22 | Event
23 |
24 | #{JSON.pretty_generate(event)}
25 |
26 | Rack Env
27 |
28 | #{JSON.pretty_generate(env)}
29 |
30 | #{context.class.name}
31 |
32 | #{CGI::escapeHTML(context.inspect)}
33 |
34 |
35 |
36 | HTML
37 | end
38 |
39 | def development?
40 | ENV.to_h
41 | .slice('RACK_ENV', 'RAILS_ENV')
42 | .values
43 | .any? { |v| v.to_s.casecmp('development').zero? }
44 | end
45 |
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/lamby/handler.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | class Handler
3 |
4 | class << self
5 |
6 | def call(app, event, context, options = {})
7 | Lamby::ColdStartMetrics.instrument! if Lamby.config.cold_start_metrics?
8 | new(app, event, context, options).call.response
9 | end
10 |
11 | end
12 |
13 | def initialize(app, event, context, options = {})
14 | @app = app
15 | @event = event
16 | @context = context
17 | @options = options
18 | end
19 |
20 | def response
21 | @response
22 | end
23 |
24 | def status
25 | @status
26 | end
27 |
28 | def headers
29 | @headers
30 | end
31 |
32 | def set_cookies
33 | return @set_cookies if defined?(@set_cookies)
34 | set_cookie = @headers.delete("Set-Cookie") || @headers.delete("set-cookie")
35 | @set_cookies = if @headers && set_cookie
36 | Array(set_cookie).flat_map { |cookie| cookie.split("\n").map(&:strip) }
37 | end
38 | end
39 |
40 | def body
41 | @rbody ||= ''.tap do |rbody|
42 | @body.each { |part| rbody << part.to_s if part }
43 | @body.close if @body.respond_to? :close
44 | end
45 | end
46 |
47 | def call
48 | @response ||= call_app
49 | self
50 | end
51 |
52 | def base64_encodeable?(hdrs = @headers)
53 | hdrs && (
54 | hdrs['content-transfer-encoding'] == 'binary' ||
55 | hdrs['Content-Transfer-Encoding'] == 'binary' ||
56 | content_encoding_compressed?(hdrs) ||
57 | hdrs['X-Lamby-Base64'] == '1'
58 | )
59 | end
60 |
61 | def body64
62 | Base64.strict_encode64(body)
63 | end
64 |
65 | private
66 |
67 | def rack
68 | return @rack if defined?(@rack)
69 | @rack = begin
70 | type = rack_option
71 | klass = Lamby::Rack.lookup type, @event
72 | (klass && klass.handle?(@event)) ? klass.new(@event, @context) : false
73 | end
74 | end
75 |
76 | def rack_option
77 | return if ENV['LAMBY_TEST_DYNAMIC_HANDLER']
78 | @options[:rack]
79 | end
80 |
81 | def rack_response
82 | { statusCode: status,
83 | headers: stringify_values!(headers),
84 | body: body }.merge(rack.response(self))
85 | end
86 |
87 | def stringify_values!(headers)
88 | headers.each do |k, v|
89 | headers[k] = v.to_s
90 | end
91 | headers
92 | end
93 |
94 | def call_app
95 | if Debug.on?(@event)
96 | Debug.call @event, @context, rack.env
97 | elsif rack?
98 | @status, @headers, @body = @app.call rack.env
99 | set_cookies
100 | rack_response
101 | elsif lambdakiq?
102 | Lambdakiq.cmd event: @event, context: @context
103 | elsif lambda_cable?
104 | LambdaCable.cmd event: @event, context: @context
105 | elsif LambdaConsole.handle?(@event)
106 | LambdaConsole.handle(@event)
107 | elsif event_bridge?
108 | Lamby.config.event_bridge_handler.call @event, @context
109 | else
110 | [404, {}, StringIO.new('')]
111 | end
112 | end
113 |
114 | def content_encoding_compressed?(hdrs)
115 | content_encoding_header = hdrs['Content-Encoding'] || hdrs['content-encoding'] || ''
116 | content_encoding_header.split(', ').any? { |h| ['br', 'gzip'].include?(h) }
117 | end
118 |
119 | def rack?
120 | rack
121 | end
122 |
123 | def event_bridge?
124 | Lamby.config.event_bridge_handler &&
125 | @event.key?('source') && @event.key?('detail') && @event.key?('detail-type')
126 | end
127 |
128 | def lambdakiq?
129 | defined?(::Lambdakiq) && ::Lambdakiq.jobs?(@event)
130 | end
131 |
132 | def lambda_cable?
133 | defined?(::LambdaCable) && ::LambdaCable.handle?(@event, @context)
134 | end
135 | end
136 | end
137 |
--------------------------------------------------------------------------------
/lib/lamby/logger.rb:
--------------------------------------------------------------------------------
1 | require 'logger'
2 |
3 | if ENV['AWS_EXECUTION_ENV']
4 | ENV['RAILS_LOG_TO_STDOUT'] = '1'
5 |
6 | module Lamby
7 | module Logger
8 |
9 | def initialize(*args, **kwargs)
10 | args[0] = STDOUT
11 | super(*args, **kwargs)
12 | end
13 |
14 | end
15 | end
16 |
17 | Logger.prepend Lamby::Logger unless ENV['LAMBY_TEST']
18 | end
19 |
--------------------------------------------------------------------------------
/lib/lamby/proxy_context.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | # This class is used by the `lamby:proxy_server` Rake task to run a
3 | # Rack server for local development proxy. Specifically, this class
4 | # accepts a JSON respresentation of a Lambda context object converted
5 | # to a Hash as the single arugment.
6 | #
7 | class ProxyContext
8 | def initialize(data)
9 | @data = data
10 | end
11 |
12 | def method_missing(method_name, *args, &block)
13 | key = method_name.to_s
14 | if @data.key?(key)
15 | @data[key]
16 | else
17 | super
18 | end
19 | end
20 |
21 | def respond_to_missing?(method_name, include_private = false)
22 | @data.key?(method_name.to_s) || super
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/lamby/proxy_server.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | class ProxyServer
3 |
4 | METHOD_NOT_ALLOWED = <<-HEREDOC.strip
5 | Method Not Allowed
6 | Please POST to this endpoint with an application/json content type and JSON payload of your Lambda's event and context.
7 |
Example: { "event": event, "context": context }
8 | HEREDOC
9 |
10 | def call(env)
11 | return method_not_allowed unless method_allowed?(env)
12 | event, context = event_and_context(env)
13 | lambda_to_rack Lamby.cmd(event: event, context: context)
14 | end
15 |
16 | private
17 |
18 | def event_and_context(env)
19 | data = env['rack.input'].dup.read
20 | json = JSON.parse(data)
21 | [ json['event'], Lamby::ProxyContext.new(json['context']) ]
22 | end
23 |
24 | def method_allowed?(env)
25 | env['REQUEST_METHOD'] == 'POST' && env['CONTENT_TYPE'] == 'application/json'
26 | end
27 |
28 | def method_not_allowed
29 | [405, {"Content-Type" => "text/html"}, [ METHOD_NOT_ALLOWED.dup ]]
30 | end
31 |
32 | def lambda_to_rack(response)
33 | [ 200, {"Content-Type" => "application/json"}, [ response.to_json ] ]
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/lamby/rack.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | class Rack
3 | LAMBDA_EVENT = 'lambda.event'.freeze
4 | LAMBDA_CONTEXT = 'lambda.context'.freeze
5 | HTTP_X_REQUESTID = 'HTTP_X_REQUEST_ID'.freeze
6 | HTTP_X_REQUEST_START = 'HTTP_X_REQUEST_START'.freeze
7 | HTTP_COOKIE = 'HTTP_COOKIE'.freeze
8 |
9 | class << self
10 |
11 | def lookup(type, event)
12 | types[type] || types.values.detect { |t| t.handle?(event) }
13 | end
14 |
15 | # Order is important. REST is hardest to isolated with handle? method.
16 | def types
17 | { alb: RackAlb,
18 | http: RackHttp,
19 | rest: RackRest,
20 | api: RackRest }
21 | end
22 |
23 | end
24 |
25 | attr_reader :event, :context
26 |
27 | def initialize(event, context)
28 | @event = event
29 | @context = context
30 | end
31 |
32 | def env
33 | @env ||= env_base.merge!(env_headers)
34 | end
35 |
36 | def response(_handler)
37 | {}
38 | end
39 |
40 | def multi_value?
41 | false
42 | end
43 |
44 | private
45 |
46 | def env_base
47 | raise NotImplementedError
48 | end
49 |
50 | def env_headers
51 | headers.transform_keys do |key|
52 | "HTTP_#{key.to_s.upcase.tr '-', '_'}"
53 | end.tap do |hdrs|
54 | hdrs[HTTP_X_REQUESTID] = request_id
55 | hdrs[HTTP_X_REQUEST_START] = "t=#{request_start}" if request_start
56 | end
57 | end
58 |
59 | def content_type
60 | headers.delete('Content-Type') || headers.delete('content-type') || headers.delete('CONTENT_TYPE')
61 | end
62 |
63 | def content_length
64 | bytesize = body.bytesize.to_s if body
65 | headers.delete('Content-Length') || headers.delete('content-length') || headers.delete('CONTENT_LENGTH') || bytesize
66 | end
67 |
68 | def body
69 | @body ||= if event['body'] && base64_encoded?
70 | Base64.decode64 event['body']
71 | else
72 | event['body']
73 | end
74 | end
75 |
76 | def headers
77 | @headers ||= event['headers'] || {}
78 | end
79 |
80 | def query_string
81 | @query_string ||= if event.key?('rawQueryString')
82 | event['rawQueryString']
83 | elsif event.key?('multiValueQueryStringParameters')
84 | query = event['multiValueQueryStringParameters'] || {}
85 | query.map do |key, value|
86 | value.map{ |v| "#{key}=#{v}" }.join('&')
87 | end.flatten.join('&')
88 | else
89 | build_query_string
90 | end
91 | end
92 |
93 | def build_query_string
94 | return if event['queryStringParameters'].nil?
95 | ::Rack::Utils.build_nested_query(
96 | event.fetch('queryStringParameters')
97 | ).gsub('[', '%5B')
98 | .gsub(']', '%5D')
99 | end
100 |
101 | def base64_encoded?
102 | event['isBase64Encoded']
103 | end
104 |
105 | def request_id
106 | context.aws_request_id
107 | end
108 |
109 | def request_start
110 | event.dig('requestContext', 'timeEpoch') ||
111 | event.dig('requestContext', 'requestTimeEpoch')
112 | end
113 |
114 | end
115 | end
116 |
--------------------------------------------------------------------------------
/lib/lamby/rack_alb.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | class RackAlb < Lamby::Rack
3 |
4 | class << self
5 |
6 | def handle?(event)
7 | event.key?('httpMethod') &&
8 | event.dig('requestContext', 'elb')
9 | end
10 |
11 | end
12 |
13 | def alb?
14 | true
15 | end
16 |
17 | def multi_value?
18 | event.key? 'multiValueHeaders'
19 | end
20 |
21 | def response(handler)
22 | hhdrs = handler.headers
23 | if multi_value?
24 | multivalue_headers = hhdrs.transform_values { |v| Array[v].compact.flatten }
25 | multivalue_headers['Set-Cookie'] = handler.set_cookies if handler.set_cookies
26 | end
27 | status_description = "#{handler.status} #{::Rack::Utils::HTTP_STATUS_CODES[handler.status]}"
28 | base64_encode = handler.base64_encodeable?(hhdrs)
29 | body = Base64.strict_encode64(handler.body) if base64_encode
30 | { multiValueHeaders: multivalue_headers,
31 | statusDescription: status_description,
32 | isBase64Encoded: base64_encode,
33 | body: body }.compact
34 | end
35 |
36 | private
37 |
38 | def env_base
39 | rack_version = defined?(::Rack::VERSION) ? ::Rack::VERSION : ::Rack.release
40 | { ::Rack::REQUEST_METHOD => event['httpMethod'],
41 | ::Rack::SCRIPT_NAME => '',
42 | ::Rack::PATH_INFO => event['path'] || '',
43 | ::Rack::QUERY_STRING => query_string,
44 | ::Rack::SERVER_NAME => headers['host'],
45 | ::Rack::SERVER_PORT => headers['x-forwarded-port'],
46 | ::Rack::SERVER_PROTOCOL => 'HTTP/1.1',
47 | ::Rack::RACK_VERSION => rack_version,
48 | ::Rack::RACK_URL_SCHEME => headers['x-forwarded-proto'],
49 | ::Rack::RACK_INPUT => StringIO.new(body || ''),
50 | ::Rack::RACK_ERRORS => $stderr,
51 | LAMBDA_EVENT => event,
52 | LAMBDA_CONTEXT => context
53 | }.tap do |env|
54 | ct = content_type
55 | cl = content_length
56 | env['CONTENT_TYPE'] = ct if ct
57 | env['CONTENT_LENGTH'] = cl if cl
58 | end
59 | end
60 |
61 | def headers
62 | @headers ||= multi_value? ? headers_multi : super
63 | end
64 |
65 | def headers_multi
66 | Hash[(event['multiValueHeaders'] || {}).map do |k,v|
67 | if v.is_a?(Array)
68 | if k == 'x-forwarded-for'
69 | [k, v.join(', ')]
70 | else
71 | [k, v.first]
72 | end
73 | else
74 | [k,v]
75 | end
76 | end]
77 | end
78 |
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/lib/lamby/rack_http.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | class RackHttp < Lamby::Rack
3 |
4 | class << self
5 |
6 | def handle?(event)
7 | event.key?('version') &&
8 | ( event.dig('requestContext', 'http') ||
9 | event.dig('requestContext', 'httpMethod') )
10 | end
11 |
12 | end
13 |
14 | def response(handler)
15 | if handler.base64_encodeable?
16 | { isBase64Encoded: true, body: handler.body64 }
17 | else
18 | super
19 | end.tap do |r|
20 | if cookies = handler.set_cookies
21 | if payload_version_one?
22 | r[:multiValueHeaders] ||= {}
23 | r[:multiValueHeaders]['Set-Cookie'] = cookies
24 | else
25 | r[:cookies] = cookies
26 | end
27 | end
28 | end
29 | end
30 |
31 | private
32 |
33 | def env_base
34 | rack_version = defined?(::Rack::VERSION) ? ::Rack::VERSION : ::Rack.release
35 | { ::Rack::REQUEST_METHOD => request_method,
36 | ::Rack::SCRIPT_NAME => '',
37 | ::Rack::PATH_INFO => path_info,
38 | ::Rack::QUERY_STRING => query_string,
39 | ::Rack::SERVER_NAME => server_name,
40 | ::Rack::SERVER_PORT => server_port,
41 | ::Rack::SERVER_PROTOCOL => server_protocol,
42 | ::Rack::RACK_VERSION => rack_version,
43 | ::Rack::RACK_URL_SCHEME => 'https',
44 | ::Rack::RACK_INPUT => StringIO.new(body || ''),
45 | ::Rack::RACK_ERRORS => $stderr,
46 | LAMBDA_EVENT => event,
47 | LAMBDA_CONTEXT => context
48 | }.tap do |env|
49 | ct = content_type
50 | cl = content_length
51 | env['CONTENT_TYPE'] = ct if ct
52 | env['CONTENT_LENGTH'] = cl if cl
53 | end
54 | end
55 |
56 | def env_headers
57 | super.tap do |hdrs|
58 | if cookies.any?
59 | hdrs[HTTP_COOKIE] = cookies.join('; ')
60 | end
61 | end
62 | end
63 |
64 | def request_method
65 | event.dig('requestContext', 'http', 'method') || event['httpMethod']
66 | end
67 |
68 | def cookies
69 | event['cookies'] || []
70 | end
71 |
72 | # Using custom domain names with v1.0 yields a good `path` parameter sans
73 | # stage. However, v2.0 and others do not. So we are just going to remove stage
74 | # no matter waht from other places for both.
75 | #
76 | def path_info
77 | stage = event.dig('requestContext', 'stage')
78 | spath = event.dig('requestContext', 'http', 'path') || event.dig('requestContext', 'path')
79 | spath = event['rawPath'] if spath != event['rawPath'] && !payload_version_one?
80 | spath.sub /\A\/#{stage}/, ''
81 | end
82 |
83 | def server_name
84 | headers['x-forwarded-host'] ||
85 | headers['X-Forwarded-Host'] ||
86 | headers['host'] ||
87 | headers['Host']
88 | end
89 |
90 | def server_port
91 | headers['x-forwarded-port'] || headers['X-Forwarded-Port']
92 | end
93 |
94 | def server_protocol
95 | event.dig('requestContext', 'http', 'protocol') ||
96 | event.dig('requestContext', 'protocol') ||
97 | 'HTTP/1.1'
98 | end
99 |
100 | def payload_version_one?
101 | event['version'] == '1.0'
102 | end
103 |
104 | end
105 | end
106 |
--------------------------------------------------------------------------------
/lib/lamby/rack_rest.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | class RackRest < Lamby::Rack
3 |
4 | class << self
5 |
6 | def handle?(event)
7 | event.key?('httpMethod')
8 | end
9 |
10 | end
11 |
12 | def response(handler)
13 | if handler.base64_encodeable?
14 | { isBase64Encoded: true, body: handler.body64 }
15 | else
16 | super
17 | end.tap do |r|
18 | if cookies = handler.set_cookies
19 | r[:multiValueHeaders] ||= {}
20 | r[:multiValueHeaders]['Set-Cookie'] = cookies
21 | end
22 | end
23 | end
24 |
25 | private
26 |
27 | def env_base
28 | rack_version = defined?(::Rack::VERSION) ? ::Rack::VERSION : ::Rack.release
29 | { ::Rack::REQUEST_METHOD => event['httpMethod'],
30 | ::Rack::SCRIPT_NAME => '',
31 | ::Rack::PATH_INFO => event['path'] || '',
32 | ::Rack::QUERY_STRING => query_string,
33 | ::Rack::SERVER_NAME => headers['Host'],
34 | ::Rack::SERVER_PORT => headers['X-Forwarded-Port'],
35 | ::Rack::SERVER_PROTOCOL => event.dig('requestContext', 'protocol') || 'HTTP/1.1',
36 | ::Rack::RACK_VERSION => rack_version,
37 | ::Rack::RACK_URL_SCHEME => 'https',
38 | ::Rack::RACK_INPUT => StringIO.new(body || ''),
39 | ::Rack::RACK_ERRORS => $stderr,
40 | LAMBDA_EVENT => event,
41 | LAMBDA_CONTEXT => context
42 | }.tap do |env|
43 | ct = content_type
44 | cl = content_length
45 | env['CONTENT_TYPE'] = ct if ct
46 | env['CONTENT_LENGTH'] = cl if cl
47 | end
48 | end
49 |
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/lamby/railtie.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | class Railtie < ::Rails::Railtie
3 | config.lamby = Lamby::Config.config
4 |
5 | rake_tasks do
6 | load 'lamby/tasks.rake'
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/lamby/ssm_parameter_store.rb:
--------------------------------------------------------------------------------
1 | require 'aws-sdk-ssm'
2 |
3 | module Lamby
4 | class SsmParameterStore
5 |
6 | MAX_RESULTS = 10
7 |
8 | Param = Struct.new :name, :env, :value
9 |
10 | attr_reader :path, :params
11 |
12 | class << self
13 |
14 | def dotenv(path)
15 | new(path).get!.to_dotenv
16 | end
17 |
18 | def get!(path)
19 | parts = path[1..-1].split('/')
20 | env = parts.pop
21 | path = "/#{parts.join('/')}"
22 | param = new(path).get!.params.detect do |p|
23 | p.env == env
24 | end
25 | param&.value
26 | end
27 |
28 | end
29 |
30 | def initialize(path, options = {})
31 | @path = path
32 | @params = []
33 | @options = options
34 | end
35 |
36 | def to_env(overwrite: true)
37 | params.each do |param|
38 | overwrite ? ENV[param.env] = param.value : ENV[param.env] ||= param.value
39 | end
40 | end
41 |
42 | def to_dotenv
43 | File.open(dotenv_file, 'a') { |f| f.write(dotenv_contents) }
44 | end
45 |
46 | def get!
47 | get_all!
48 | get_history! unless label.to_s.empty?
49 | self
50 | end
51 |
52 | def label
53 | ENV['LAMBY_SSM_PARAMS_LABEL'] || @options[:label]
54 | end
55 |
56 | def client
57 | @client ||= begin
58 | options = @options[:client_options] || {}
59 | Aws::SSM::Client.new(options)
60 | end
61 | end
62 |
63 |
64 | private
65 |
66 | def dotenv_file
67 | @options[:dotenv_file] || ENV['LAMBY_SSM_PARAMS_FILE'] || Rails.root.join(".env.#{Rails.env}")
68 | end
69 |
70 | def dotenv_contents
71 | params.each_with_object('') do |param, contents|
72 | line = "#{param.env}=#{param.value}\n"
73 | contents << line
74 | end
75 | end
76 |
77 | # Path
78 |
79 | def get_all!
80 | return params if @got_all
81 | get_parse_all
82 | while @all_response.next_token do get_parse_all end
83 | @got_all = true
84 | params
85 | end
86 |
87 | def get_parse_all
88 | get_all
89 | parse_all
90 | end
91 |
92 | def get_all
93 | @all_response = client.get_parameters_by_path(get_all_options)
94 | end
95 |
96 | def get_all_options
97 | { path: path,
98 | recursive: true,
99 | with_decryption: true,
100 | max_results: MAX_RESULTS
101 | }.tap { |options|
102 | token = @all_response&.next_token
103 | options[:next_token] = token if token
104 | }
105 | end
106 |
107 | def parse_all
108 | @all_response.parameters.each do |p|
109 | env = p.name.split('/').last
110 | params << Param.new(p.name, env, p.value)
111 | end
112 | end
113 |
114 | # History
115 |
116 | def get_history!
117 | return params if @got_history
118 | params.each do |param|
119 | name = param.name
120 | get_parse_history(name)
121 | while @hist_response.next_token do get_parse_history(name) end
122 | end
123 | @got_history = true
124 | params
125 | end
126 |
127 | def get_parse_history(name)
128 | get_history(name)
129 | parse_history(name)
130 | end
131 |
132 | def get_history(name)
133 | @hist_response = client.get_parameter_history(get_history_options(name))
134 | end
135 |
136 | def get_history_options(name)
137 | { name: name,
138 | with_decryption: true,
139 | max_results: MAX_RESULTS
140 | }.tap { |options|
141 | token = @hist_response&.next_token
142 | options[:next_token] = token if token
143 | }
144 | end
145 |
146 | def parse_history(name)
147 | @hist_response.parameters.each do |p|
148 | next unless p.labels.include? label
149 | param = params.detect { |param| param.name == name }
150 | param.value = p.value
151 | end
152 | end
153 |
154 | end
155 | end
156 |
--------------------------------------------------------------------------------
/lib/lamby/tasks.rake:
--------------------------------------------------------------------------------
1 | namespace :lamby do
2 | task :proxy_server => [:environment] do
3 | require 'webrick'
4 | port = ENV['LAMBY_PROXY_PORT'] || 3000
5 | bind = ENV['LAMBY_PROXY_BIND'] || '0.0.0.0'
6 | Rack::Handler::WEBrick.run Lamby::ProxyServer.new, Port: port, BindAddress: bind
7 | end
8 |
9 | task :proxy_server_puma => [:environment] do
10 | port = ENV['LAMBY_PROXY_PORT'] || 3000
11 | host = ENV['LAMBY_PROXY_BIND'] || '0.0.0.0'
12 | lamby_proxy = Lamby::ProxyServer.new
13 | maybe_later = MaybeLater::Middleware.new(lamby_proxy)
14 | server = Puma::Server.new(maybe_later)
15 | server.add_tcp_listener host, port
16 | puts "Starting Puma server on #{host}:#{port}..."
17 | server.run.join
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/lamby/version.rb:
--------------------------------------------------------------------------------
1 | module Lamby
2 | VERSION = '6.0.1'
3 | end
4 |
--------------------------------------------------------------------------------
/test/cold_start_metrics_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ColdStartMetricsSpec < LambySpec
4 |
5 | before { Lamby::ColdStartMetrics.clear! }
6 |
7 | it 'has a config that defaults to false' do
8 | refute Lamby.config.cold_start_metrics?
9 | end
10 |
11 | it 'calling instrument for the first time will output a CloudWatch count metric for ColdStart' do
12 | out = capture(:stdout) { Lamby::ColdStartMetrics.instrument! }
13 | metric = JSON.parse(out)
14 | expect(metric['AppName']).must_equal 'Dummy'
15 | expect(metric['ColdStart']).must_equal 1
16 | metrics = metric['_aws']['CloudWatchMetrics']
17 | expect(metrics.size).must_equal 1
18 | expect(metrics.first['Namespace']).must_equal 'Lamby'
19 | expect(metrics.first['Dimensions']).must_equal [['AppName']]
20 | expect(metrics.first['Metrics']).must_equal [{'Name' => 'ColdStart', 'Unit' => 'Count'}]
21 | end
22 |
23 | it 'only ever sends one metric for the lifespan of the function' do
24 | assert_output(/ColdStart/) { Lamby::ColdStartMetrics.instrument! }
25 | assert_output('') { Lamby::ColdStartMetrics.instrument! }
26 | Timecop.travel(Time.now + 10) { assert_output('') { Lamby::ColdStartMetrics.instrument! } }
27 | Timecop.travel(Time.now + 50000000) { assert_output('') { Lamby::ColdStartMetrics.instrument! } }
28 | end
29 |
30 | it 'will record a ProactiveInit metric if the function is called after 10 seconds' do
31 | Timecop.travel(Time.now + 11) do
32 | out = capture(:stdout) { Lamby::ColdStartMetrics.instrument! }
33 | metric = JSON.parse(out)
34 | expect(metric['AppName']).must_equal 'Dummy'
35 | expect(metric['ProactiveInit']).must_equal 1
36 | metrics = metric['_aws']['CloudWatchMetrics']
37 | expect(metrics.size).must_equal 1
38 | expect(metrics.first['Namespace']).must_equal 'Lamby'
39 | expect(metrics.first['Dimensions']).must_equal [['AppName']]
40 | expect(metrics.first['Metrics']).must_equal [{'Name' => 'ProactiveInit', 'Unit' => 'Count'}]
41 | end
42 | end
43 |
44 | it 'will not record a ProactiveInit metric if the function is called before 10 seconds' do
45 | Timecop.travel(Time.now + 9) do
46 | assert_output(/ColdStart/) { Lamby::ColdStartMetrics.instrument! }
47 | end
48 | end
49 |
50 | private
51 |
52 | def now_ms
53 | (Time.now.to_f * 1000).to_i
54 | end
55 |
56 | end
57 |
--------------------------------------------------------------------------------
/test/debug_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class DebugTest < LambySpec
4 | describe '#on?' do
5 | let(:event) do
6 | TestHelpers::Events::HttpV2.create(
7 | 'queryStringParameters' => {
8 | 'debug' => debug_param
9 | }
10 | )
11 | end
12 |
13 | before do
14 | @old_rack_env = ENV['RACK_ENV']
15 | @old_rails_env = ENV['RAILS_ENV']
16 | ENV['RACK_ENV'] = rack_env
17 | ENV['RAILS_ENV'] = rails_env
18 | end
19 |
20 | after do
21 | ENV['RACK_ENV'] = @old_rack_env
22 | ENV['RAILS_ENV'] = @old_rails_env
23 | end
24 |
25 | describe 'when RACK_ENV == development' do
26 | let(:rack_env) { 'development' }
27 | let(:rails_env) { nil }
28 |
29 | describe 'when the debug param is 1' do
30 | let(:debug_param) { '1' }
31 |
32 | it 'returns true' do
33 | expect(Lamby::Debug.on?(event)).must_equal true
34 | end
35 | end
36 |
37 | describe 'when the debug param is nil' do
38 | let(:debug_param) { nil }
39 |
40 | it 'returns false' do
41 | expect(Lamby::Debug.on?(event)).must_equal false
42 | end
43 | end
44 | end
45 |
46 | describe 'when RAILS_ENV == development' do
47 | let(:rack_env) { 'production' }
48 | let(:rails_env) { 'development' }
49 |
50 | describe 'when the debug param is 1' do
51 | let(:debug_param) { '1' }
52 |
53 | it 'returns true' do
54 | expect(Lamby::Debug.on?(event)).must_equal true
55 | end
56 | end
57 |
58 | describe 'when the debug param is nil' do
59 | let(:debug_param) { nil }
60 |
61 | it 'returns false' do
62 | expect(Lamby::Debug.on?(event)).must_equal false
63 | end
64 | end
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/test/dummy_app/Rakefile:
--------------------------------------------------------------------------------
1 | require_relative 'init'
2 | Dummy::Application.load_tasks
3 |
--------------------------------------------------------------------------------
/test/dummy_app/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link application.css
3 | //= link application.js
4 |
--------------------------------------------------------------------------------
/test/dummy_app/app/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/app/controllers/.keep
--------------------------------------------------------------------------------
/test/dummy_app/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | helper_method :logged_in?
3 |
4 | def index
5 | render
6 | end
7 |
8 | def image
9 | data = File.read Rails.root.join('app/images/1.png')
10 | send_data data, type: 'image/png', disposition: 'inline'
11 | end
12 |
13 | def login
14 | if params[:password] == 'password'
15 | session[:logged_in] = 'true'
16 | end
17 | redirect_to root_url
18 | end
19 |
20 | def logout
21 | reset_session
22 | redirect_to root_url
23 | end
24 |
25 | def exception
26 | raise 'hell'
27 | end
28 |
29 | def percent
30 | render
31 | end
32 |
33 | def cooks
34 | cookies['1'] = '1'
35 | cookies['2'] = '2'
36 | render :index
37 | end
38 |
39 | private
40 |
41 | def logged_in?
42 | session[:logged_in] == 'true'
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/test/dummy_app/app/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/app/images/1.png
--------------------------------------------------------------------------------
/test/dummy_app/app/models/dummy/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/app/models/dummy/.keep
--------------------------------------------------------------------------------
/test/dummy_app/app/views/application/index.html.erb:
--------------------------------------------------------------------------------
1 | Hello Lamby
2 |
--------------------------------------------------------------------------------
/test/dummy_app/app/views/application/percent.html.erb:
--------------------------------------------------------------------------------
1 | Params: <%= params[:path] %>
2 | Request Path: <%= request.path %>
3 |
--------------------------------------------------------------------------------
/test/dummy_app/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%= yield %>
7 | <%= logged_in? %>
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/dummy_app/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../config/application', __dir__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/test/dummy_app/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'rake'
3 | Rake.application.run
4 |
--------------------------------------------------------------------------------
/test/dummy_app/config.ru:
--------------------------------------------------------------------------------
1 | require ::File.expand_path('../init', __FILE__)
2 | run Dummy::Application
3 |
--------------------------------------------------------------------------------
/test/dummy_app/config/boot.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/config/boot.rb
--------------------------------------------------------------------------------
/test/dummy_app/config/initializers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/config/initializers/.keep
--------------------------------------------------------------------------------
/test/dummy_app/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.config.secret_key_base = '012345678901234567890123456789'
2 |
--------------------------------------------------------------------------------
/test/dummy_app/config/routes.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.routes.draw do
2 | root to: 'application#index'
3 | get 'image' => 'application#image'
4 | post 'login', to: 'application#login'
5 | delete 'logout', to: 'application#logout'
6 | get 'exception', to: 'application#exception'
7 | get 'percent/*path', to: 'application#percent'
8 | get 'cooks', to: 'application#cooks'
9 | get 'redirect_test', to: redirect('/')
10 | end
11 |
--------------------------------------------------------------------------------
/test/dummy_app/init.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | ENV["RAILS_SERVE_STATIC_FILES"] = '1'
3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __FILE__)
4 | require 'bundler/setup'
5 | require 'rails'
6 | require 'active_model/railtie'
7 | require 'active_job/railtie'
8 | require 'action_controller/railtie'
9 | require 'action_mailer/railtie'
10 | require 'action_view/railtie'
11 | require 'rails/test_unit/railtie'
12 | Bundler.require(:default, Rails.env)
13 |
14 | module Dummy
15 | class Application < ::Rails::Application
16 |
17 | # Basic Engine
18 | config.root = File.join __FILE__, '..'
19 | config.cache_store = :memory_store
20 | config.secret_key_base = '012345678901234567890123456789'
21 | config.active_support.test_order = :random
22 | config.logger = Logger.new('/dev/null')
23 | config.public_file_server.enabled = true
24 | config.public_file_server.headers = {
25 | 'Cache-Control' => "public, max-age=2592000",
26 | 'X-Lamby-Base64' => '1'
27 | }
28 | # Mimic production environment.
29 | config.consider_all_requests_local = false
30 | config.action_dispatch.show_exceptions = :all
31 | # Mimic test environment.
32 | config.action_controller.perform_caching = false
33 | config.action_controller.allow_forgery_protection = false
34 | config.action_mailer.delivery_method = :test
35 | config.active_support.deprecation = :stderr
36 | config.allow_concurrency = true
37 | config.cache_classes = true
38 | config.dependency_loading = true
39 | config.preload_frameworks = true
40 | config.eager_load = true
41 | config.middleware.insert_after ActionDispatch::Static, Rack::Deflater, sync: false if ENV['LAMBY_RACK_DEFLATE_ENABLED']
42 | config.active_job.queue_adapter = :lambdakiq
43 | end
44 | end
45 |
46 | Dummy::Application.initialize!
47 |
--------------------------------------------------------------------------------
/test/dummy_app/public/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/public/.keep
--------------------------------------------------------------------------------
/test/dummy_app/public/1-public.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/public/1-public.png
--------------------------------------------------------------------------------
/test/dummy_app/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy_app/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy_app/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/dummy_app/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/test/dummy_app/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/test/dummy_app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rails-lambda/lamby/95d083e1568597f31841ad8920d71a44f2e694e5/test/dummy_app/public/favicon.ico
--------------------------------------------------------------------------------
/test/dummy_app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/test/handler_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class HandlerTest < LambySpec
4 |
5 | let(:app) { Rack::Builder.new { run Rails.application }.to_app }
6 | let(:context) { TestHelpers::LambdaContext.new }
7 |
8 |
9 | describe 'http-v2' do
10 | it 'returns the correct rack response' do
11 | event = TestHelpers::Events::HttpV2.create
12 | handler = Lamby::Handler.new(app, event, context, rack: :http)
13 | handler.call
14 | response = handler.send(:rack_response)
15 |
16 | expect(response[:statusCode]).must_equal 200
17 | expect(response[:headers]['Content-Type']).must_equal 'text/html; charset=utf-8'
18 | expect(response[:body]).must_match %r{Hello Lamby
}
19 | expect(response.keys).must_equal [:statusCode, :headers, :body]
20 | end
21 |
22 | it 'get' do
23 | event = TestHelpers::Events::HttpV2.create
24 | result = Lamby.handler app, event, context, rack: :http
25 | expect(result[:statusCode]).must_equal 200
26 | expect(result[:body]).must_match %r{Hello Lamby
}
27 | expect(result[:body]).must_match %r{false
}
28 | end
29 |
30 | it 'head' do
31 | event = TestHelpers::Events::HttpV2.create(
32 | 'requestContext' => {'http' => {'method' => 'HEAD'}},
33 | 'body' => nil
34 | )
35 | result = Lamby.handler app, event, context, rack: :http
36 | expect(result[:statusCode]).must_equal 200
37 | expect(result[:body]).must_equal ""
38 | end
39 |
40 | it 'get - multiple cookies' do
41 | event = TestHelpers::Events::HttpV2.create(
42 | 'rawPath' => '/production/cooks',
43 | 'requestContext' => { 'http' => {'path' => '/production/cooks'} }
44 | )
45 | result = Lamby.handler app, event, context, rack: :http
46 | expect(result[:statusCode]).must_equal 200
47 | expect(result[:headers]['Set-Cookie']).must_be_nil
48 | expect(result[:cookies]).must_equal ["1=1; path=/", "2=2; path=/"]
49 | expect(result[:body]).must_match %r{Hello Lamby
}
50 | end
51 |
52 | it 'get - image' do
53 | event = TestHelpers::Events::HttpV2.create(
54 | 'rawPath' => '/production/image',
55 | 'requestContext' => { 'http' => {'path' => '/production/image'} }
56 | )
57 | result = Lamby.handler app, event, context, rack: :http
58 | expect(result[:statusCode]).must_equal 200
59 | expect(result[:body]).must_equal encode64(dummy_app_image)
60 | expect(result[:headers]['content-type']).must_equal 'image/png'
61 | expect(result[:isBase64Encoded]).must_equal true
62 | # Public file server.
63 | event = TestHelpers::Events::HttpV2.create(
64 | 'rawPath' => '/production/1-public.png',
65 | 'requestContext' => { 'http' => {'path' => '/production/1-public.png'} }
66 | )
67 | result = Lamby.handler app, event, context, rack: :http
68 | expect(result[:statusCode]).must_equal 200
69 | expect(result[:body]).must_equal encode64(dummy_app_image_public), 'not'
70 | expect(result[:headers]['content-type']).must_equal 'image/png'
71 | expect(result[:headers]['Cache-Control']).must_equal 'public, max-age=2592000'
72 | expect(result[:headers]['X-Lamby-Base64']).must_equal '1'
73 | expect(result[:isBase64Encoded]).must_equal true
74 | end
75 |
76 | it 'post - login' do
77 | event = TestHelpers::Events::HttpV2Post.create
78 | result = Lamby.handler app, event, context, rack: :http
79 | expect(result[:statusCode]).must_equal 302
80 | expect(result[:headers]['Location']).must_equal 'https://myawesomelambda.example.com/'
81 | # Check logged in state via GET.
82 | event = TestHelpers::Events::HttpV2.create(
83 | 'cookies' => [session_cookie(result)]
84 | )
85 | result = Lamby.handler app, event, context, rack: :http
86 | expect(result[:statusCode]).must_equal 200
87 | expect(result[:body]).must_match %r{true
}
88 | end
89 |
90 | it 'get - exception' do
91 | event = TestHelpers::Events::HttpV2.create(
92 | 'rawPath' => '/production/exception',
93 | 'requestContext' => { 'http' => {'path' => '/production/exception'} }
94 | )
95 | result = Lamby.handler app, event, context, rack: :http
96 | expect(result[:statusCode]).must_equal 500
97 | expect(result[:body]).must_match %r{We're sorry, but something went wrong.}
98 | expect(result[:body]).must_match %r{This file lives in public/500.html}
99 | end
100 |
101 | it 'get - percent' do
102 | event = TestHelpers::Events::HttpV2.create(
103 | 'rawPath' => '/production/percent/dwef782jkif%3d',
104 | 'requestContext' => { 'http' => {'path' => '/production/percent/dwef782jkif='} }
105 | )
106 | result = Lamby.handler app, event, context, rack: :http
107 | expect(result[:statusCode]).must_equal 200
108 | expect(result[:body]).must_match %r{Params: dwef782jkif=}
109 | expect(result[:body]).must_match %r{Request Path: /percent/dwef782jkif%3}
110 | end
111 |
112 | end
113 |
114 | describe 'http-v1' do
115 |
116 | it 'returns the correct rack response' do
117 | event = TestHelpers::Events::HttpV1.create
118 | handler = Lamby::Handler.new(app, event, context, rack: :http)
119 | handler.call
120 | response = handler.send(:rack_response)
121 |
122 | expect(response[:statusCode]).must_equal 200
123 | expect(response[:headers]['Content-Type']).must_equal 'text/html; charset=utf-8'
124 | expect(response[:body]).must_match %r{Hello Lamby
}
125 | expect(response.keys).must_equal [:statusCode, :headers, :body]
126 | end
127 |
128 | it 'get' do
129 | event = TestHelpers::Events::HttpV1.create
130 | result = Lamby.handler app, event, context, rack: :http
131 | expect(result[:statusCode]).must_equal 200
132 | expect(result[:body]).must_match %r{Hello Lamby
}
133 | expect(result[:body]).must_match %r{false
}
134 | end
135 |
136 | it 'head' do
137 | event = TestHelpers::Events::HttpV1.create(
138 | 'httpMethod' => 'HEAD',
139 | 'requestContext' => {'httpMethod' => 'HEAD'},
140 | 'body' => nil
141 | )
142 | result = Lamby.handler app, event, context, rack: :http
143 | expect(result[:statusCode]).must_equal 200
144 | expect(result[:body]).must_equal ""
145 | end
146 |
147 | it 'get - multiple cookies' do
148 | event = TestHelpers::Events::HttpV1.create(
149 | 'path' => '/production/cooks',
150 | 'requestContext' => { 'path' => '/production/cooks'}
151 | )
152 | result = Lamby.handler app, event, context, rack: :http
153 | expect(result[:statusCode]).must_equal 200
154 | expect(result[:headers]['Set-Cookie']).must_be_nil
155 | expect(result[:multiValueHeaders]['Set-Cookie']).must_equal ["1=1; path=/", "2=2; path=/"]
156 | expect(result[:body]).must_match %r{Hello Lamby
}
157 | end
158 |
159 | it 'get - image' do
160 | event = TestHelpers::Events::HttpV1.create(
161 | 'path' => '/production/image',
162 | 'requestContext' => { 'path' => '/production/image' }
163 | )
164 | result = Lamby.handler app, event, context, rack: :http
165 | expect(result[:statusCode]).must_equal 200
166 | expect(result[:body]).must_equal encode64(dummy_app_image)
167 | expect(result[:headers]['content-type']).must_equal 'image/png'
168 | # Public file server.
169 | event = TestHelpers::Events::HttpV1.create(
170 | 'path' => '/production/1-public.png',
171 | 'requestContext' => { 'path' => '/production/1-public.png' }
172 | )
173 | result = Lamby.handler app, event, context, rack: :http
174 | expect(result[:statusCode]).must_equal 200
175 | expect(result[:body]).must_equal encode64(dummy_app_image_public), 'not'
176 | expect(result[:headers]['content-type']).must_equal 'image/png'
177 | expect(result[:headers]['Cache-Control']).must_equal 'public, max-age=2592000'
178 | expect(result[:headers]['X-Lamby-Base64']).must_equal '1'
179 | expect(result[:isBase64Encoded]).must_equal true
180 | end
181 |
182 | it 'post - login' do
183 | event = TestHelpers::Events::HttpV1Post.create
184 | result = Lamby.handler app, event, context, rack: :http
185 | expect(result[:statusCode]).must_equal 302
186 | expect(result[:headers]['Location']).must_equal 'https://myawesomelambda.example.com/'
187 | # Check logged in state via GET.
188 | event = TestHelpers::Events::HttpV1.create(
189 | 'headers' => { 'cookie' => session_cookie(result) },
190 | 'multiValueHeaders' => { 'cookie' => [ session_cookie(result) ]}
191 | )
192 | result = Lamby.handler app, event, context, rack: :http
193 | expect(result[:statusCode]).must_equal 200
194 | expect(result[:body]).must_match %r{true
}
195 | end
196 |
197 | it 'get - exception' do
198 | event = TestHelpers::Events::HttpV1.create(
199 | 'path' => '/production/exception',
200 | 'requestContext' => { 'path' => '/production/exception' }
201 | )
202 | result = Lamby.handler app, event, context, rack: :http
203 | expect(result[:statusCode]).must_equal 500
204 | expect(result[:body]).must_match %r{We're sorry, but something went wrong.}
205 | expect(result[:body]).must_match %r{This file lives in public/500.html}
206 | end
207 |
208 | end
209 |
210 | describe 'rest' do
211 |
212 | it 'get' do
213 | event = TestHelpers::Events::Rest.create
214 | result = Lamby.handler app, event, context, rack: :rest
215 | expect(result[:statusCode]).must_equal 200
216 | expect(result[:body]).must_match %r{Hello Lamby
}
217 | expect(result[:body]).must_match %r{false
}
218 | end
219 |
220 | it 'head' do
221 | event = TestHelpers::Events::Rest.create(
222 | 'httpMethod' => 'HEAD',
223 | 'body' => nil
224 | )
225 | result = Lamby.handler app, event, context, rack: :rest
226 | expect(result[:statusCode]).must_equal 200
227 | expect(result[:body]).must_equal ""
228 | end
229 |
230 | it 'get - multiple cookies' do
231 | event = TestHelpers::Events::Rest.create(
232 | 'path' => '/cooks',
233 | 'requestContext' => { 'path' => '/cooks'}
234 | )
235 | result = Lamby.handler app, event, context, rack: :rest
236 | expect(result[:statusCode]).must_equal 200
237 | expect(result[:headers]['Set-Cookie']).must_be_nil
238 | expect(result[:multiValueHeaders]['Set-Cookie']).must_equal ["1=1; path=/", "2=2; path=/"]
239 | expect(result[:body]).must_match %r{Hello Lamby
}
240 | end
241 |
242 | it 'get - image' do
243 | event = TestHelpers::Events::Rest.create(
244 | 'path' => '/image',
245 | 'requestContext' => { 'path' => '/image' }
246 | )
247 | result = Lamby.handler app, event, context, rack: :rest
248 | expect(result[:statusCode]).must_equal 200
249 | expect(result[:body]).must_equal encode64(dummy_app_image)
250 | expect(result[:headers]['content-type']).must_equal 'image/png'
251 | # Public file server.
252 | event = TestHelpers::Events::Rest.create(
253 | 'path' => '/1-public.png',
254 | 'requestContext' => { 'path' => '/1-public.png' }
255 | )
256 | result = Lamby.handler app, event, context, rack: :rest
257 | expect(result[:statusCode]).must_equal 200
258 | expect(result[:body]).must_equal encode64(dummy_app_image_public), 'not'
259 | expect(result[:headers]['content-type']).must_equal 'image/png'
260 | expect(result[:headers]['Cache-Control']).must_equal 'public, max-age=2592000'
261 | expect(result[:headers]['X-Lamby-Base64']).must_equal '1'
262 | expect(result[:isBase64Encoded]).must_equal true
263 | end
264 |
265 | it 'post - login' do
266 | event = TestHelpers::Events::RestPost.create
267 | result = Lamby.handler app, event, context, rack: :rest
268 | expect(result[:statusCode]).must_equal 302
269 | expect(result[:headers]['Location']).must_equal 'https://myawesomelambda.example.com/'
270 | # Check logged in state via GET.
271 | event = TestHelpers::Events::Rest.create(
272 | 'headers' => { 'Cookie' => session_cookie(result) },
273 | 'multiValueHeaders' => { 'Cookie' => [ session_cookie(result) ]}
274 | )
275 | result = Lamby.handler app, event, context, rack: :rest
276 | expect(result[:statusCode]).must_equal 200
277 | expect(result[:body]).must_match %r{true
}
278 | end
279 |
280 | it 'get - exception' do
281 | event = TestHelpers::Events::Rest.create(
282 | 'path' => '/exception',
283 | 'requestContext' => { 'path' => '/exception' }
284 | )
285 | result = Lamby.handler app, event, context, rack: :rest
286 | expect(result[:statusCode]).must_equal 500
287 | expect(result[:body]).must_match %r{We're sorry, but something went wrong.}
288 | expect(result[:body]).must_match %r{This file lives in public/500.html}
289 | end
290 |
291 | end
292 |
293 | describe 'alb' do
294 |
295 | it 'get' do
296 | event = TestHelpers::Events::Alb.create
297 | result = Lamby.handler app, event, context, rack: :alb
298 | expect(result[:statusCode]).must_equal 200
299 | expect(result[:body]).must_match %r{Hello Lamby
}
300 | expect(result[:body]).must_match %r{false
}
301 | end
302 |
303 | it 'head' do
304 | event = TestHelpers::Events::Alb.create(
305 | 'httpMethod' => 'HEAD',
306 | 'body' => nil
307 | )
308 | result = Lamby.handler app, event, context, rack: :alb
309 | expect(result[:statusCode]).must_equal 200
310 | expect(result[:body]).must_equal ""
311 | end
312 |
313 | it 'get - multiple cookies' do
314 | event = TestHelpers::Events::Alb.create 'path' => '/cooks'
315 | result = Lamby.handler app, event, context, rack: :rest
316 | expect(result[:statusCode]).must_equal 200
317 | expect(result[:headers]['Set-Cookie']).must_be_nil
318 | expect(result[:multiValueHeaders]['Set-Cookie']).must_equal ["1=1; path=/", "2=2; path=/"]
319 | expect(result[:body]).must_match %r{Hello Lamby
}
320 | end
321 |
322 | it 'get - image' do
323 | event = TestHelpers::Events::Alb.create 'path' => '/image'
324 | result = Lamby.handler app, event, context, rack: :alb
325 | expect(result[:statusCode]).must_equal 200
326 | expect(result[:body]).must_equal encode64(dummy_app_image)
327 | expect(result[:headers]['content-type']).must_equal 'image/png'
328 | event = TestHelpers::Events::Alb.create 'path' => '/1-public.png'
329 | result = Lamby.handler app, event, context, rack: :alb
330 | expect(result[:statusCode]).must_equal 200
331 | expect(result[:body]).must_equal encode64(dummy_app_image_public), 'not'
332 | expect(result[:headers]['content-type']).must_equal 'image/png'
333 | expect(result[:headers]['Cache-Control']).must_equal 'public, max-age=2592000'
334 | expect(result[:headers]['X-Lamby-Base64']).must_equal '1'
335 | expect(result[:isBase64Encoded]).must_equal true
336 | end
337 |
338 | it 'get - exception' do
339 | event = TestHelpers::Events::Alb.create(
340 | 'path' => '/exception',
341 | 'requestContext' => { 'path' => '/exception' }
342 | )
343 | result = Lamby.handler app, event, context, rack: :alb
344 | expect(result[:statusCode]).must_equal 500
345 | expect(result[:body]).must_match %r{We're sorry, but something went wrong.}
346 | expect(result[:body]).must_match %r{This file lives in public/500.html}
347 | end
348 |
349 | end
350 |
351 | describe 'event_bridge' do
352 |
353 | let(:event) do
354 | {
355 | "version" => "0",
356 | "id"=>"0874bcac-1dac-2393-637f-201025f217b0",
357 | "detail-type"=>"orderCreated",
358 | "source"=>"com.myorg.stores",
359 | "account"=>"123456789012",
360 | "time"=>"2021-04-29T13:51:41Z",
361 | "region"=>"us-east-1",
362 | "resources"=>[],
363 | "detail"=>{"id" => "123"}
364 | }
365 | end
366 |
367 | it 'has a configurable proc' do
368 | expect(Lamby.config.event_bridge_handler).must_be_instance_of Proc
369 | Lamby.config.event_bridge_handler = lambda { |e,c| "#{e}#{c}" }
370 | r = Lamby.config.event_bridge_handler.call(1,2)
371 | expect(r).must_equal '12'
372 | end
373 |
374 | it 'basic event puts to log' do
375 | out = capture(:stdout) { @result = Lamby.handler app, event, context }
376 | expect(out).must_match %r{0874bcac-1dac-2393-637f-201025f217b0}
377 | end
378 |
379 | end
380 |
381 | describe 'lambdakiq' do
382 |
383 | let(:event) do
384 | {
385 | "Records" => [
386 | {
387 | "messageId" => "9081fe74-bc79-451f-a03a-2fe5c6e2f807",
388 | "receiptHandle" => "AQEBgbn8GmF1fMo4z3IIqlJYymS6e7NBynwE+LsQlzjjdcKtSIomGeKMe0noLC9UDShUSe8bzr0s+pby03stHNRv1hgg4WRB5YT4aO0dwOuio7LvMQ/VW88igQtWmca78K6ixnU9X5Sr6J+/+WMvjBgIdvO0ycAM2tyJ1nxRHs/krUoLo/bFCnnwYh++T5BLQtFjFGrRkPjWnzjAbLWKU6Hxxr5lkHSxGhjfAoTCOjhi9crouXaWD+H1uvoGx/O/ZXaeMNjKIQoKjhFguwbEpvrq2Pfh2x9nRgBP3cKa9qw4Q3oFQ0MiQAvnK+UO8cCnsKtD",
389 | "body" => "{\"job_class\":\"TestHelpers::Jobs::BasicJob\",\"job_id\":\"527cd37e-08f4-4aa8-9834-a46220cdc5a3\",\"provider_job_id\":null,\"queue_name\":\"lambdakiq-JobsQueue-TESTING123.fifo\",\"priority\":null,\"arguments\":[\"test\"],\"executions\":0,\"exception_executions\":{},\"locale\":\"en\",\"timezone\":\"UTC\",\"enqueued_at\":\"2020-11-30T13:07:36Z\"}",
390 | "attributes" => {
391 | "ApproximateReceiveCount" => "1",
392 | "SentTimestamp" => "1606741656429",
393 | "SequenceNumber" => "18858069937755376128",
394 | "MessageGroupId" => "527cd37e-08f4-4aa8-9834-a46220cdc5a3",
395 | "SenderId" => "AROA4DJKY67RBVYCN5UZ3",
396 | "MessageDeduplicationId" => "527cd37e-08f4-4aa8-9834-a46220cdc5a3",
397 | "ApproximateFirstReceiveTimestamp" => "1606741656429"
398 | },
399 | "messageAttributes" => {
400 | "lambdakiq" => {
401 | "stringValue" => "1",
402 | "stringListValues" => [],
403 | "binaryListValues" => [],
404 | "dataType" => "String"
405 | }
406 | },
407 | "md5OfMessageAttributes" => "5fde2d817e4e6b7f28735d3b1725f817",
408 | "md5OfBody" => "6477b54fb64dde974ea7514e87d3b8a5",
409 | "eventSource" => "aws:sqs",
410 | "eventSourceARN" => "arn:aws:sqs:us-east-1:831702759394:lambdakiq-JobsQueue-TESTING123.fifo", "awsRegion" => "us-east-1"
411 | }
412 | ]
413 | }
414 | end
415 |
416 | it 'basic event' do
417 | out = capture(:stdout) { @result = Lamby.handler app, event, context }
418 | expect(out).must_match %r{BasicJob with: "test"}
419 | expect(@result).must_be_instance_of Hash
420 | expect(@result[:batchItemFailures]).must_be_instance_of Array
421 | expect(@result[:batchItemFailures]).must_be_empty
422 | end
423 |
424 | end
425 |
426 | describe 'LambdaConsole' do
427 |
428 | after do
429 | Lamby.config.runner_patterns.push %r{.*}
430 | end
431 |
432 | it 'run' do
433 | event = { 'X_LAMBDA_CONSOLE' => { 'run' => %q|echo 'hello'| } }
434 | result = Lamby.handler app, event, context
435 | expect(result[:statusCode]).must_equal 0
436 | expect(result[:headers]).must_equal({})
437 | expect(result[:body]).must_match %r{hello}
438 | end
439 |
440 | it 'run with error' do
441 | event = { 'X_LAMBDA_CONSOLE' => { 'run' => %q|/usr/bin/doesnotexist| } }
442 | result = Lamby.handler app, event, context
443 | expect(result[:statusCode]).must_equal 1
444 | expect(result[:headers]).must_equal({})
445 | expect(result[:body]).must_match %r{No such file or directory}
446 | end
447 |
448 | it 'run with pattern' do
449 | Lamby.config.runner_patterns.clear
450 | event = { 'X_LAMBDA_CONSOLE' => { 'run' => %q|echo 'hello'| } }
451 | error = assert_raises LambdaConsole::Run::UnknownCommandPattern do
452 | Lamby.handler app, event, context
453 | end
454 | expect(error.message).must_equal %|echo 'hello'|
455 | end
456 |
457 | it 'interact' do
458 | event = { 'X_LAMBDA_CONSOLE' => { 'interact' => 'Object.new' } }
459 | result = Lamby.handler app, event, context
460 | expect(result[:statusCode]).must_equal 200
461 | expect(result[:headers]).must_equal({})
462 | expect(result[:body]).must_match %r{#}
463 | end
464 |
465 | it 'interact with error' do
466 | event = { 'X_LAMBDA_CONSOLE' => { 'interact' => 'raise("hell")' } }
467 | result = Lamby.handler app, event, context
468 | expect(result[:statusCode]).must_equal 422
469 | expect(result[:headers]).must_equal({})
470 | expect(result[:body]).must_match %r{#}
471 | end
472 |
473 | end
474 |
475 | private
476 |
477 | def session_cookie(result)
478 | cookies = (result[:cookies] || result[:multiValueHeaders]['Set-Cookie'])[0]
479 | cookies.split('; ').detect { |x| x =~ /session=/ }
480 | end
481 |
482 | end
483 |
--------------------------------------------------------------------------------
/test/lamby_core_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class LambyCoreSpec < LambySpec
4 | def setup
5 | @event = { 'httpMethod' => 'GET', 'path' => '/' }
6 | @context = TestHelpers::LambdaContext.new
7 | end
8 |
9 | it 'has a version number' do
10 | expect(Lamby::VERSION).wont_be_nil
11 | end
12 |
13 | it 'catches SIGTERM signal' do
14 | assert_raises(SystemExit) do
15 | Process.kill('TERM', Process.pid)
16 | sleep 0.1 # Give time for the signal to be processed
17 | end
18 | end
19 |
20 | it 'catches SIGINT signal' do
21 | assert_raises(SystemExit) do
22 | Process.kill('INT', Process.pid)
23 | sleep 0.1 # Give time for the signal to be processed
24 | end
25 | end
26 |
27 | it 'executes cmd method' do
28 | Lamby.config.stubs(:handled_proc).returns(->(_, _) {})
29 | result = Lamby.cmd(event: @event, context: @context)
30 |
31 | assert result.is_a?(Hash), "Expected result to be a Hash, but got #{result.class}"
32 | assert_equal 200, result[:statusCode], "Expected statusCode to be 200, but got #{result[:statusCode]}"
33 | assert_includes result[:body], "Hello Lamby", "Expected body to contain 'Hello Lamby'"
34 | end
35 |
36 | it 'executes handler method' do
37 | app = Rack::Builder.new do
38 | run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
39 | end.to_app
40 |
41 | event = {'httpMethod' => 'GET'}
42 | result = Lamby.handler(app, event, @context)
43 |
44 | assert result.is_a?(Hash), "Expected result to be a Hash, but got #{result.class}"
45 | assert_equal 200, result[:statusCode], "Expected statusCode to be 200, but got #{result[:statusCode]}"
46 | assert_equal 'OK', result[:body], "Expected body to be 'OK', but got #{result[:body]}"
47 | end
48 |
49 | it 'returns the configuration' do
50 | config = Lamby.config
51 | assert config.is_a?(Lamby::Configuration), "Expected config to be an instance of Lamby::Config, but got #{config.class}"
52 | end
53 | end
--------------------------------------------------------------------------------
/test/proxy_context_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ProxyContextTest < LambySpec
4 |
5 | let(:context_data) { TestHelpers::LambdaContext.raw_data }
6 | let(:proxy_context) { Lamby::ProxyContext.new(context_data) }
7 |
8 | it 'should respond to all context methods' do
9 | context_data.keys.each do |key|
10 | response = proxy_context.respond_to?(key.to_sym)
11 | expect(response).must_equal true, "Expected context to respond to #{key.inspect}"
12 | end
13 | end
14 |
15 | it 'should return the correct value for each context method' do
16 | expect(proxy_context.clock_diff).must_equal 1681486457423
17 | expect(proxy_context.deadline_ms).must_equal 1681492072985
18 | expect(proxy_context.aws_request_id).must_equal "d6f5961b-5034-4db5-b3a9-fa378133b0f0"
19 | end
20 |
21 | it 'should raise an error for unknown methods' do
22 | expect { proxy_context.foo }.must_raise NoMethodError
23 | end
24 |
25 | it 'should return false for respond_to? for a unknown method' do
26 | expect(proxy_context.respond_to?(:foo)).must_equal false
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/test/proxy_server_test.rb:
--------------------------------------------------------------------------------
1 | require 'net/http'
2 | require 'test_helper'
3 |
4 | class ProxyServerTest < LambySpec
5 | include Rack::Test::Methods
6 |
7 | let(:event) { TestHelpers::Events::HttpV2.create }
8 | let(:context) { TestHelpers::LambdaContext.raw_data }
9 | let(:app) { Rack::Builder.new { run Lamby::ProxyServer.new }.to_app }
10 | let(:json) { {"event": event, "context": context}.to_json }
11 |
12 | it 'should return a 405 helpful message on GET' do
13 | response = get '/'
14 | expect(response.status).must_equal 405
15 | expect(response.headers).must_equal({"content-type"=>"text/html", "content-length"=>"233"})
16 | expect(response.body).must_include 'Method Not Allowed'
17 | end
18 |
19 | it 'should call Lamby.cmd on POST and include full response as JSON' do
20 | response = post '/', json, 'CONTENT_TYPE' => 'application/json'
21 | expect(response.status).must_equal 200
22 | expect(response.headers).must_equal({"content-type"=>"application/json", "content-length"=>"740"})
23 | response_body = JSON.parse(response.body)
24 | expect(response_body['statusCode']).must_equal 200
25 | expect(response_body['headers']).must_be_kind_of Hash
26 | expect(response_body['body']).must_match 'Hello Lamby'
27 | end
28 |
29 | it 'will return whatever Lamby.cmd does' do
30 | Lamby.stubs(:cmd).returns({statusCode: 200})
31 | response = post '/', json, 'CONTENT_TYPE' => 'application/json'
32 | expect(response.status).must_equal 200
33 | expect(response.headers).must_equal({"content-type"=>"application/json", "content-length"=>"18"})
34 | response_body = JSON.parse(response.body)
35 | expect(response_body['statusCode']).must_equal 200
36 | expect(response_body['headers']).must_be_nil
37 | expect(response_body['body']).must_be_nil
38 | end
39 |
40 | it 'will use the configured Lamby rack_app' do
41 | rack_app = Rack::Builder.new { run lambda { |env| [200, {}, StringIO.new('OK')] } }.to_app
42 | Lamby.config.rack_app = rack_app
43 | response = post '/', json, 'CONTENT_TYPE' => 'application/json'
44 | expect(response.status).must_equal 200
45 | expect(response.headers).must_equal({"content-type"=>"application/json", "content-length"=>"43"})
46 | response_body = JSON.parse(response.body)
47 | expect(response_body['statusCode']).must_equal 200
48 | expect(response_body['headers']).must_equal({})
49 | expect(response_body['body']).must_equal('OK')
50 | end
51 |
52 | end
53 |
--------------------------------------------------------------------------------
/test/rack_alb_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RackAlbTest < LambySpec
4 |
5 | let(:context) { TestHelpers::LambdaContext.new }
6 |
7 | it 'env' do
8 | event = TestHelpers::Events::Alb.create
9 | rack = Lamby::RackAlb.new event, context
10 | expect(rack.env['REQUEST_METHOD']).must_equal 'GET'
11 | expect(rack.env['SCRIPT_NAME']).must_equal ''
12 | expect(rack.env['PATH_INFO']).must_equal '/'
13 | expect(rack.env['QUERY_STRING']).must_equal 'colors[]=blue&colors[]=red'
14 | expect(rack.env['SERVER_NAME']).must_equal 'myawesomelambda.example.com'
15 | expect(rack.env['SERVER_PORT']).must_equal '443'
16 | expect(rack.env['SERVER_PROTOCOL']).must_equal 'HTTP/1.1'
17 | expect(rack.env['rack.url_scheme']).must_equal 'https'
18 | expect(rack.env['HTTP_ACCEPT']).must_equal 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
19 | expect(rack.env['HTTP_ACCEPT_ENCODING']).must_equal 'gzip'
20 | expect(rack.env['HTTP_HOST']).must_equal 'myawesomelambda.example.com'
21 | expect(rack.env['HTTP_USER_AGENT']).must_equal 'Amazon CloudFront'
22 | expect(rack.env['HTTP_VIA']).must_equal '2.0 3dc5b7040885724e78019cc31f0ef3d9.cloudfront.net (CloudFront)'
23 | expect(rack.env['HTTP_X_AMZ_CF_ID']).must_equal 'BSlDkHoVD8-009TATJzymLqSBzViE_6jj7DlkiJkub-PpDb8wI4Pxw=='
24 | expect(rack.env['HTTP_X_AMZN_TRACE_ID']).must_equal 'Root=1-5e7c160a-0a9065c7a28a428cd8b98215'
25 | expect(rack.env['HTTP_X_FORWARDED_FOR']).must_equal '72.218.219.201, 72.218.219.201, 34.195.252.132'
26 | expect(rack.env['HTTP_X_FORWARDED_PORT']).must_equal '443'
27 | expect(rack.env['HTTP_X_FORWARDED_PROTO']).must_equal 'https'
28 | expect(rack.env['HTTP_X_REQUEST_ID']).must_equal 'a59284fd-d48c-4de5-af9e-df4254489ac2'
29 | expect(rack.env['HTTP_COOKIE']).must_equal 'signal1=test; signal2=control'
30 | end
31 |
32 | end
33 |
--------------------------------------------------------------------------------
/test/rack_deflate_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RackDeflateTest < LambySpec
4 |
5 | let(:app) { Rack::Builder.new { run Rails.application }.to_app }
6 | let(:context) { TestHelpers::LambdaContext.new }
7 |
8 | it 'get - Rest' do
9 | event = TestHelpers::Events::Rest.create
10 | result = Lamby.handler app, event, context, rack: :rest
11 | expect(result[:statusCode]).must_equal 200
12 | end
13 |
14 | it 'head - Rest' do
15 | event = TestHelpers::Events::Rest.create(
16 | 'httpMethod' => 'HEAD',
17 | 'body' => nil
18 | )
19 | result = Lamby.handler app, event, context, rack: :rest
20 | expect(result[:statusCode]).must_equal 200
21 | end
22 |
23 | it 'get - Rest with redirect' do
24 | event = TestHelpers::Events::Rest.create(
25 | 'path' => '/redirect_test',
26 | 'requestContext' => { 'path' => '/redirect_test'}
27 | )
28 | result = Lamby.handler app, event, context, rack: :rest
29 | expect(result[:statusCode]).must_equal 301
30 | refute result[:headers]['Location'].nil?
31 | end
32 |
33 | it 'get - HttpV1' do
34 | event = TestHelpers::Events::HttpV1.create
35 | result = Lamby.handler app, event, context, rack: :http
36 | expect(result[:statusCode]).must_equal 200
37 | end
38 |
39 | it 'head - HttpV1' do
40 | event = TestHelpers::Events::HttpV1.create(
41 | 'httpMethod' => 'HEAD',
42 | 'requestContext' => {'httpMethod' => 'HEAD'},
43 | 'body' => nil
44 | )
45 | result = Lamby.handler app, event, context, rack: :http
46 | expect(result[:statusCode]).must_equal 200
47 | end
48 |
49 | it 'get - HttpV1 with redirect' do
50 | event = TestHelpers::Events::HttpV1.create(
51 | 'path' => '/production/redirect_test',
52 | 'requestContext' => { 'path' => '/production/redirect_test'}
53 | )
54 | result = Lamby.handler app, event, context, rack: :http
55 | expect(result[:statusCode]).must_equal 301
56 | refute result[:headers]['Location'].nil?
57 | end
58 |
59 | it 'get - HttpV2' do
60 | event = TestHelpers::Events::HttpV2.create
61 | result = Lamby.handler app, event, context, rack: :http
62 | expect(result[:statusCode]).must_equal 200
63 | end
64 |
65 | it 'head - HttpV2' do
66 | event = TestHelpers::Events::HttpV2.create(
67 | 'requestContext' => {'http' => {'method' => 'HEAD'}},
68 | 'body' => nil
69 | )
70 | result = Lamby.handler app, event, context, rack: :http
71 | expect(result[:statusCode]).must_equal 200
72 | end
73 |
74 | it 'get - HttpV2 with redirect' do
75 | event = TestHelpers::Events::HttpV2.create(
76 | 'rawPath' => '/production/redirect_test',
77 | 'requestContext' => { 'http' => {'path' => '/production/redirect_test'} }
78 | )
79 | result = Lamby.handler app, event, context, rack: :http
80 | expect(result[:statusCode]).must_equal 301
81 | refute result[:headers]['Location'].nil?
82 | end
83 |
84 | it 'get - Alb' do
85 | event = TestHelpers::Events::Alb.create
86 | result = Lamby.handler app, event, context, rack: :alb
87 | expect(result[:statusCode]).must_equal 200
88 | end
89 |
90 | it 'get - Alb with redirect' do
91 | event = TestHelpers::Events::Alb.create 'path' => '/redirect_test'
92 | result = Lamby.handler app, event, context, rack: :alb
93 | expect(result[:statusCode]).must_equal 301
94 | refute result[:headers]['Location'].nil?
95 | end
96 |
97 | end
98 |
--------------------------------------------------------------------------------
/test/rack_http_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RackHttpTest < LambySpec
4 |
5 | let(:context) { TestHelpers::LambdaContext.new }
6 |
7 | describe 'v1' do
8 |
9 | it 'env' do
10 | event = TestHelpers::Events::HttpV1.create
11 | rack = Lamby::RackHttp.new event, context
12 | expect(rack.env['REQUEST_METHOD']).must_equal 'GET'
13 | expect(rack.env['SCRIPT_NAME']).must_equal ''
14 | expect(rack.env['PATH_INFO']).must_equal '/'
15 | expect(rack.env['QUERY_STRING']).must_equal 'colors[]=blue&colors[]=red'
16 | expect(rack.env['SERVER_NAME']).must_equal 'myawesomelambda.example.com'
17 | expect(rack.env['SERVER_PORT']).must_equal '443'
18 | expect(rack.env['SERVER_PROTOCOL']).must_equal 'HTTP/1.1'
19 | expect(rack.env['rack.url_scheme']).must_equal 'https'
20 | expect(rack.env['HTTP_ACCEPT']).must_equal 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
21 | expect(rack.env['HTTP_ACCEPT_ENCODING']).must_equal 'gzip, deflate, br'
22 | expect(rack.env['HTTP_HOST']).must_equal 'myawesomelambda.example.com'
23 | expect(rack.env['HTTP_USER_AGENT']).must_equal 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15'
24 | expect(rack.env['HTTP_VIA']).must_be_nil
25 | expect(rack.env['HTTP_X_AMZ_CF_ID']).must_be_nil
26 | expect(rack.env['HTTP_X_AMZN_TRACE_ID']).must_equal 'Root=1-5e7fe714-fee6909429159440eb352c40'
27 | expect(rack.env['HTTP_X_FORWARDED_FOR']).must_equal '72.218.219.201'
28 | expect(rack.env['HTTP_X_FORWARDED_PORT']).must_equal '443'
29 | expect(rack.env['HTTP_X_FORWARDED_PROTO']).must_equal 'https'
30 | expect(rack.env['HTTP_X_REQUEST_ID']).must_equal 'a59284fd-d48c-4de5-af9e-df4254489ac2'
31 | expect(rack.env['HTTP_COOKIE']).must_equal 'signal1=test; signal2=control'
32 | end
33 |
34 | end
35 |
36 | describe 'v2' do
37 |
38 | it 'env' do
39 | event = TestHelpers::Events::HttpV2.create
40 | rack = Lamby::RackHttp.new event, context
41 | expect(rack.env['REQUEST_METHOD']).must_equal 'GET'
42 | expect(rack.env['SCRIPT_NAME']).must_equal ''
43 | expect(rack.env['PATH_INFO']).must_equal '/'
44 | expect(rack.env['QUERY_STRING']).must_equal 'colors[]=blue&colors[]=red'
45 | expect(rack.env['SERVER_NAME']).must_equal 'myawesomelambda.example.com'
46 | expect(rack.env['SERVER_PORT']).must_equal '443'
47 | expect(rack.env['SERVER_PROTOCOL']).must_equal 'HTTP/1.1'
48 | expect(rack.env['rack.url_scheme']).must_equal 'https'
49 | expect(rack.env['HTTP_ACCEPT']).must_equal 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
50 | expect(rack.env['HTTP_ACCEPT_ENCODING']).must_equal 'gzip, deflate, br'
51 | expect(rack.env['HTTP_HOST']).must_equal 'myawesomelambda.example.com'
52 | expect(rack.env['HTTP_USER_AGENT']).must_equal 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15'
53 | expect(rack.env['HTTP_VIA']).must_be_nil
54 | expect(rack.env['HTTP_X_AMZ_CF_ID']).must_be_nil
55 | expect(rack.env['HTTP_X_AMZN_TRACE_ID']).must_equal 'Root=1-5e7fe714-fee6909429159440eb352c40'
56 | expect(rack.env['HTTP_X_FORWARDED_FOR']).must_equal '72.218.219.201'
57 | expect(rack.env['HTTP_X_FORWARDED_PORT']).must_equal '443'
58 | expect(rack.env['HTTP_X_FORWARDED_PROTO']).must_equal 'https'
59 | expect(rack.env['HTTP_X_REQUEST_ID']).must_equal 'a59284fd-d48c-4de5-af9e-df4254489ac2'
60 | expect(rack.env['HTTP_COOKIE']).must_equal 'signal1=test; signal2=control'
61 | end
62 |
63 | end
64 |
65 | end
66 |
--------------------------------------------------------------------------------
/test/rack_rest_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RackRestTest < LambySpec
4 |
5 | let(:context) { TestHelpers::LambdaContext.new }
6 |
7 | it 'env' do
8 | event = TestHelpers::Events::Rest.create
9 | rack = Lamby::RackRest.new event, context
10 | expect(rack.env['REQUEST_METHOD']).must_equal 'GET'
11 | expect(rack.env['SCRIPT_NAME']).must_equal ''
12 | expect(rack.env['PATH_INFO']).must_equal '/'
13 | expect(rack.env['QUERY_STRING']).must_equal 'colors[]=blue&colors[]=red'
14 | expect(rack.env['SERVER_NAME']).must_equal '4o8v9z4feh.execute-api.us-east-1.amazonaws.com'
15 | expect(rack.env['SERVER_PORT']).must_equal '443'
16 | expect(rack.env['SERVER_PROTOCOL']).must_equal 'HTTP/1.1'
17 | expect(rack.env['rack.url_scheme']).must_equal 'https'
18 | expect(rack.env['HTTP_ACCEPT']).must_equal 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
19 | expect(rack.env['HTTP_ACCEPT_ENCODING']).must_equal 'gzip'
20 | expect(rack.env['HTTP_HOST']).must_equal '4o8v9z4feh.execute-api.us-east-1.amazonaws.com'
21 | expect(rack.env['HTTP_USER_AGENT']).must_equal 'Amazon CloudFront'
22 | expect(rack.env['HTTP_VIA']).must_equal '2.0 7f7e359e1c06a914d3d305785359b84d.cloudfront.net (CloudFront)'
23 | expect(rack.env['HTTP_X_AMZ_CF_ID']).must_equal 'kXZzJ72NOsZSsPu-JzNUGyFei1G0r9uzoup3yHrwk4J5qGLKrdUrRA=='
24 | expect(rack.env['HTTP_X_AMZN_TRACE_ID']).must_equal 'Root=1-5e7fe714-fee6909429159440eb352c40'
25 | expect(rack.env['HTTP_X_FORWARDED_FOR']).must_equal '72.218.219.201, 34.195.252.119'
26 | expect(rack.env['HTTP_X_FORWARDED_PORT']).must_equal '443'
27 | expect(rack.env['HTTP_X_FORWARDED_PROTO']).must_equal 'https'
28 | expect(rack.env['HTTP_X_REQUEST_ID']).must_equal 'a59284fd-d48c-4de5-af9e-df4254489ac2'
29 | expect(rack.env['HTTP_COOKIE']).must_equal 'signal1=test; signal2=control'
30 | end
31 |
32 | end
33 |
--------------------------------------------------------------------------------
/test/ssm_parameter_store_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class SsmParameterStoreTest < LambySpec
4 |
5 | let(:klass) { Lamby::SsmParameterStore }
6 | let(:path) { path_debug || '/config/staging/app/env' }
7 | let(:file) { File.expand_path('../.env.staging', __FILE__) }
8 |
9 | after { clear! }
10 |
11 | describe '#to_env' do
12 | before do
13 | ENV['FOO'] = 'test'
14 | end
15 |
16 | it 'overwrites existing environment variables by default' do
17 | envs = klass.new path
18 | stub_params(envs)
19 | assert ENV.key?('FOO')
20 | refute ENV.key?('BAR')
21 | envs.to_env
22 | expect(ENV['FOO']).must_equal 'foo'
23 | expect(ENV['BAR']).must_equal 'bar'
24 | end
25 |
26 | it 'does not overwrite existing environment variables when overwrite flag set to false' do
27 | envs = klass.new path
28 | stub_params(envs)
29 | assert ENV.key?('FOO')
30 | refute ENV.key?('BAR')
31 | envs.to_env(overwrite: false)
32 | expect(ENV['FOO']).must_equal 'test'
33 | expect(ENV['BAR']).must_equal 'bar'
34 | end
35 | end
36 |
37 | it '#to_dotenv' do
38 | envs = klass.new path, dotenv_file: file
39 | stub_params(envs)
40 | envs.to_dotenv
41 | expect(File.read(file)).must_equal <<~EOF
42 | FOO=foo
43 | BAR=bar
44 | EOF
45 | end
46 |
47 | private
48 |
49 | def stub_params(envs)
50 | envs.stubs(:params).returns([
51 | param("#{path}/FOO", 'FOO', 'foo'),
52 | param("#{path}/BAR", 'BAR', 'bar')
53 | ])
54 | end
55 |
56 | def param(name, env, value)
57 | klass::Param.new(name, env, value)
58 | end
59 |
60 | def path_debug
61 | ENV['LAMBY_DEBUG_SSM_PATH'] if debug?
62 | end
63 |
64 | def debug?
65 | ENV['LAMBY_DEBUG_SSM'].present?
66 | end
67 |
68 | def clear!
69 | ENV.delete 'FOO'
70 | ENV.delete 'BAR'
71 | FileUtils.rm_rf(file)
72 | end
73 |
74 | end
75 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['LAMBY_TEST'] = '1'
2 | ENV['AWS_EXECUTION_ENV'] = 'AWS_Lambda_Image'
3 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4 | require 'lamby'
5 | require 'pry'
6 | require 'timecop'
7 | require 'minitest/autorun'
8 | require 'minitest/focus'
9 | require 'mocha/minitest'
10 | require 'dummy_app/init'
11 | require 'test_helper/dummy_app_helpers'
12 | require 'test_helper/stream_helpers'
13 | require 'test_helper/lambda_context'
14 | require 'test_helper/event_helpers'
15 | require 'test_helper/jobs_helpers'
16 | require 'test_helper/lambdakiq_helpers'
17 |
18 | Rails.backtrace_cleaner.remove_silencers!
19 | Lambdakiq::Client.default_options.merge! stub_responses: true
20 | Timecop.safe_mode = true
21 |
22 | class LambySpec < Minitest::Spec
23 | include TestHelpers::DummyAppHelpers,
24 | TestHelpers::StreamHelpers,
25 | TestHelpers::LambdakiqHelpers
26 |
27 | before do
28 | Lamby.config.reconfigure
29 | lambdakiq_client_reset!
30 | lambdakiq_client_stub_responses
31 | end
32 |
33 | after do
34 | Timecop.return
35 | end
36 |
37 | private
38 |
39 | def encode64(v)
40 | Base64.strict_encode64(v)
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/test/test_helper/dummy_app_helpers.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module DummyAppHelpers
3 |
4 | extend ActiveSupport::Concern
5 |
6 | private
7 |
8 | def dummy_app
9 | ::Dummy::Application
10 | end
11 |
12 | def dummy_root
13 | dummy_app.root
14 | end
15 |
16 | def dummy_config
17 | dummy_app.config
18 | end
19 |
20 | def dummy_app_image
21 | File.read dummy_root.join('app/images/1.png')
22 | end
23 |
24 | def dummy_app_image_public
25 | File.read dummy_root.join('public/1-public.png')
26 | end
27 |
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/test/test_helper/event_helpers.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper/events/base'
2 | require 'test_helper/events/rest'
3 | require 'test_helper/events/rest_post'
4 | require 'test_helper/events/alb'
5 | require 'test_helper/events/http_v1'
6 | require 'test_helper/events/http_v1_post'
7 | require 'test_helper/events/http_v2'
8 | require 'test_helper/events/http_v2_post'
9 |
--------------------------------------------------------------------------------
/test/test_helper/events/alb.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module Events
3 | class Alb < Base
4 |
5 | self.event = {
6 | "requestContext" => {
7 | "elb" => {
8 | "targetGroupArn" => "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/myawesomelambda-1rndy3u8psl2j/3f1bcbeec09c9050"}
9 | },
10 | "httpMethod" => "GET",
11 | "path" => "/",
12 | "multiValueQueryStringParameters" => {"colors[]" => ["blue", "red"]},
13 | "multiValueHeaders" => {
14 | "accept" => ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],
15 | "accept-encoding" => ["gzip"],
16 | "connection" => ["Keep-Alive"],
17 | "cookie" => ["signal1=test; signal2=control"],
18 | "host" => ["myawesomelambda.example.com"],
19 | "user-agent" => ["Amazon CloudFront"],
20 | "via" => ["2.0 3dc5b7040885724e78019cc31f0ef3d9.cloudfront.net (CloudFront)"],
21 | "x-amz-cf-id" => ["BSlDkHoVD8-009TATJzymLqSBzViE_6jj7DlkiJkub-PpDb8wI4Pxw=="],
22 | "x-amzn-trace-id" => ["Root=1-5e7c160a-0a9065c7a28a428cd8b98215"],
23 | "x-forwarded-for" => ["72.218.219.201", "72.218.219.201", "34.195.252.132"],
24 | "x-forwarded-host" => ["myawesomelambda.example.com"],
25 | "x-forwarded-port" => ["443"],
26 | "x-forwarded-proto" => ["https"]
27 | },
28 | "body" => "",
29 | "isBase64Encoded" => false
30 | }.freeze
31 |
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/test/test_helper/events/base.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module Events
3 | class Base
4 |
5 | class_attribute :event, instance_writer: false
6 | self.event = Hash.new
7 |
8 | def self.create(overrides = {})
9 | event.deep_merge(overrides.stringify_keys)
10 | end
11 |
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/test/test_helper/events/http_v1.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module Events
3 | class HttpV1 < Base
4 | # Via Custom Domain Name integration.
5 | #
6 | self.event = {
7 | "version" => "1.0",
8 | "resource" => "$default",
9 | "path" => "/",
10 | "httpMethod" => "GET",
11 | "headers" => {
12 | "Content-Length" => "0",
13 | "Host" => "myawesomelambda.example.com",
14 | "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15",
15 | "X-Amzn-Trace-Id" => "Root=1-5e7fe714-fee6909429159440eb352c40",
16 | "X-Forwarded-For" => "72.218.219.201",
17 | "X-Forwarded-Port" => "443",
18 | "X-Forwarded-Proto" => "https",
19 | "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
20 | "accept-encoding" => "gzip, deflate, br",
21 | "accept-language" => "en-us",
22 | "cookie" => "signal1=test; signal2=control"
23 | },
24 | "multiValueHeaders" => {
25 | "Content-Length" => ["0"],
26 | "Host" => ["myawesomelambda.example.com"],
27 | "User-Agent" => ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15"],
28 | "X-Amzn-Trace-Id" => ["Root=1-5e7fe714-fee6909429159440eb352c40"],
29 | "X-Forwarded-For" => ["72.218.219.201"],
30 | "X-Forwarded-Port" => ["443"],
31 | "X-Forwarded-Proto" => ["https"],
32 | "accept" => ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],
33 | "accept-encoding" => ["gzip, deflate, br"],
34 | "accept-language" => ["en-us"]
35 | },
36 | "queryStringParameters" => {
37 | "colors[]" => "red"
38 | },
39 | "multiValueQueryStringParameters" => {
40 | "colors[]" => ["blue", "red"]
41 | },
42 | "requestContext" => {
43 | "accountId" => nil,
44 | "apiId" => "n12pmpajak",
45 | "domainName" => "myawesomelambda.example.com",
46 | "domainPrefix" => "myawesomelambda",
47 | "extendedRequestId" => "KSCL-irBIAMEJIA=",
48 | "httpMethod" => "GET",
49 | "identity" => {
50 | "accessKey" => nil,
51 | "accountId" => nil,
52 | "caller" => nil,
53 | "cognitoAuthenticationProvider" => nil,
54 | "cognitoAuthenticationType" => nil,
55 | "cognitoIdentityId" => nil,
56 | "cognitoIdentityPoolId" => nil,
57 | "principalOrgId" => nil,
58 | "sourceIp" => "72.218.219.201",
59 | "user" => nil,
60 | "userAgent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15",
61 | "userArn" => nil
62 | },
63 | "path" => "/production/",
64 | "protocol" => "HTTP/1.1",
65 | "requestId" => "KSCL-irBIAMEJIA=",
66 | "requestTime" => "01/Apr/2020:00:44:22 +0000",
67 | "requestTimeEpoch" => 1585701862143,
68 | "resourceId" => "$default",
69 | "resourcePath" => "$default",
70 | "stage" => "production"
71 | },
72 | "pathParameters" => nil,
73 | "stageVariables" => nil,
74 | "body" => nil,
75 | "isBase64Encoded" => false
76 | }.freeze
77 |
78 | # Via CloudFront directly to API Gateway w/Origin Path.
79 | #
80 | # self.event = {
81 | # "version" => "1.0",
82 | # "resource" => "$default",
83 | # "path" => "/production/",
84 | # "httpMethod" => "GET",
85 | # "headers" => {
86 | # "Content-Length" => "0",
87 | # "Host" => "n12pmpajak.execute-api.us-east-1.amazonaws.com",
88 | # "User-Agent" => "Amazon CloudFront",
89 | # "X-Amz-Cf-Id" => "XPTzwjMoVu5Vlp6QLBl_f1NNa2IXRpF_LAFJ9isoq_Pb4MUarItT0w==",
90 | # "X-Amzn-Trace-Id" => "Root=1-5e83cbd2-bfd143859e9214f4860e2779",
91 | # "X-Forwarded-For" => "72.218.219.201, 3.231.2.50",
92 | # "X-Forwarded-Host" => "myawesomelambda.example.com",
93 | # "X-Forwarded-Port" => "443",
94 | # "X-Forwarded-Proto" => "https",
95 | # "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
96 | # "accept-encoding" => "gzip",
97 | # "via" => "2.0 613faec4b883bfe2ebdd8a74d5006f4c.cloudfront.net (CloudFront)"
98 | # },
99 | # "multiValueHeaders" => {
100 | # "Content-Length" => ["0"],
101 | # "Host" => ["n12pmpajak.execute-api.us-east-1.amazonaws.com"],
102 | # "User-Agent" => ["Amazon CloudFront"],
103 | # "X-Amz-Cf-Id" => ["XPTzwjMoVu5Vlp6QLBl_f1NNa2IXRpF_LAFJ9isoq_Pb4MUarItT0w=="],
104 | # "X-Amzn-Trace-Id" => ["Root=1-5e83cbd2-bfd143859e9214f4860e2779"],
105 | # "X-Forwarded-For" => ["72.218.219.201, 3.231.2.50"],
106 | # "X-Forwarded-Host" => ["myawesomelambda.example.com"],
107 | # "X-Forwarded-Port" => ["443"],
108 | # "X-Forwarded-Proto" => ["https"],
109 | # "accept" => ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],
110 | # "accept-encoding" => ["gzip"],
111 | # "via" => ["2.0 613faec4b883bfe2ebdd8a74d5006f4c.cloudfront.net (CloudFront)"]
112 | # },
113 | # "queryStringParameters" => {
114 | # "colors[]" => "red"
115 | # },
116 | # "multiValueQueryStringParameters" => {
117 | # "colors[]" => ["blue", "red"]
118 | # },
119 | # "requestContext" => {
120 | # "accountId" => nil,
121 | # "apiId" => "n12pmpajak",
122 | # "domainName" => "n12pmpajak.execute-api.us-east-1.amazonaws.com",
123 | # "domainPrefix" => "n12pmpajak",
124 | # "extendedRequestId" => "KRzI1i2uoAMEPCA=",
125 | # "httpMethod" => "GET",
126 | # "identity" => {
127 | # "accessKey" => nil,
128 | # "accountId" => nil,
129 | # "caller" => nil,
130 | # "cognitoAuthenticationProvider" => nil,
131 | # "cognitoAuthenticationType" => nil,
132 | # "cognitoIdentityId" => nil,
133 | # "cognitoIdentityPoolId" => nil,
134 | # "principalOrgId" => nil,
135 | # "sourceIp" => "3.231.2.50",
136 | # "user" => nil,
137 | # "userAgent" => "Amazon CloudFront",
138 | # "userArn" => nil
139 | # },
140 | # "path" => "/production/",
141 | # "protocol" => "HTTP/1.1",
142 | # "requestId" => "KRzI1i2uoAMEPCA=",
143 | # "requestTime" => "31/Mar/2020:23:01:38 +0000",
144 | # "requestTimeEpoch" => 1585695698070,
145 | # "resourceId" => "$default",
146 | # "resourcePath" => "$default",
147 | # "stage" => "production"
148 | # },
149 | # "pathParameters" => nil,
150 | # "stageVariables" => nil,
151 | # "body" => nil,
152 | # "isBase64Encoded" => false
153 | # }.freeze
154 | end
155 | end
156 | end
--------------------------------------------------------------------------------
/test/test_helper/events/http_v1_post.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module Events
3 | class HttpV1Post < Base
4 | # Via Custom Domain Name integration.
5 | #
6 | self.event = {
7 | "version" => "1.0",
8 | "resource" => "$default",
9 | "path" => "/login",
10 | "httpMethod" => "POST",
11 | "headers" => {
12 | "content-length" => "144",
13 | "content-type" => "application/x-www-form-urlencoded",
14 | "host" => "myawesomelambda.example.com",
15 | "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15",
16 | "x-amzn-trace-id" => "Root=1-5e83e682-ab153f7a267a9a904369faa6",
17 | "x-forwarded-for" => "72.218.219.201",
18 | "x-forwarded-port" => "443",
19 | "x-forwarded-proto" => "https",
20 | "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
21 | "accept-encoding" => "gzip, deflate, br",
22 | "accept-language" => "en-us",
23 | "cookie" => "signal1=test; signal2=control",
24 | "origin" => "https://myawesomelambda.example.com",
25 | "referer" => "https://myawesomelambda.example.com/?colors[]=blue&colors[]=red"
26 | },
27 | "multiValueHeaders" => {
28 | "content-length" => ["144"],
29 | "content-type" => ["application/x-www-form-urlencoded"],
30 | "host" => ["myawesomelambda.example.com"],
31 | "user-agent" => ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15"],
32 | "x-amzn-trace-id" => ["Root=1-5e83e682-ab153f7a267a9a904369faa6"],
33 | "x-forwarded-for" => ["72.218.219.201"],
34 | "x-forwarded-port" => ["443"],
35 | "x-forwarded-proto" => ["https"],
36 | "accept" => ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],
37 | "accept-encoding" => ["gzip, deflate, br"],
38 | "accept-language" => ["en-us"],
39 | "cookie" => ["signal1=test; signal2=control"],
40 | "origin" => ["https://myawesomelambda.example.com"],
41 | "referer" => ["https://myawesomelambda.example.com/?colors[]=blue&colors[]=red"]
42 | },
43 | "queryStringParameters" => nil,
44 | "multiValueQueryStringParameters" => nil,
45 | "requestContext" => {
46 | "accountId" => nil,
47 | "apiId" => "n12pmpajak",
48 | "domainName" => "myawesomelambda.example.com",
49 | "domainPrefix" => "myawesomelambda",
50 | "extendedRequestId" => "KSD0ZgV2oAMESNg=",
51 | "httpMethod" => "POST",
52 | "identity" => {
53 | "accessKey" => nil,
54 | "accountId" => nil,
55 | "caller" => nil,
56 | "cognitoAuthenticationProvider" => nil,
57 | "cognitoAuthenticationType" => nil,
58 | "cognitoIdentityId" => nil,
59 | "cognitoIdentityPoolId" => nil,
60 | "principalOrgId" => nil,
61 | "sourceIp" => "72.218.219.201",
62 | "user" => nil,
63 | "userAgent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15",
64 | "userArn" => nil
65 | },
66 | "path" => "/production/login",
67 | "protocol" => "HTTP/1.1",
68 | "requestId" => "KSD0ZgV2oAMESNg=",
69 | "requestTime" => "01/Apr/2020:00:55:30 +0000",
70 | "requestTimeEpoch" => 1585702530405,
71 | "resourceId" => "$default",
72 | "resourcePath" => "$default",
73 | "stage" => "production"
74 | },
75 | "pathParameters" => nil,
76 | "stageVariables" => nil,
77 | "body" => "YXV0aGVudGljaXR5X3Rva2VuPVBNbThhY1FCZkZsVTBtbVltbTdOZnhmNktxJTJGWDRlVHRuMXIwYngzWWlJemFjeTRlUURVbk13MHJFUG5GNXVZbk9MRm5QbnlCcXQxVFlzemRMSDBtOUElM0QlM0QmcGFzc3dvcmQ9cGFzc3dvcmQmY29tbWl0PUxvZ2lu",
78 | "isBase64Encoded" => true
79 | }.freeze
80 | end
81 | end
82 | end
--------------------------------------------------------------------------------
/test/test_helper/events/http_v2.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module Events
3 | class HttpV2 < Base
4 |
5 | # Via Custom Domain Name integration.
6 | #
7 | self.event = {
8 | "version" => "2.0",
9 | "routeKey" => "$default",
10 | "rawPath" => "/production/",
11 | "rawQueryString" => "colors[]=blue&colors[]=red",
12 | "cookies" => ["signal1=test", "signal2=control"],
13 | "headers" => {
14 | "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
15 | "accept-encoding" => "gzip, deflate, br",
16 | "accept-language" => "en-us",
17 | "content-length" => "0",
18 | "host" => "myawesomelambda.example.com",
19 | "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15",
20 | "x-amzn-trace-id" => "Root=1-5e7fe714-fee6909429159440eb352c40",
21 | "x-forwarded-for" => "72.218.219.201",
22 | "x-forwarded-port" => "443",
23 | "x-forwarded-proto" => "https"
24 | },
25 | "requestContext" => {
26 | "accountId" => nil,
27 | "apiId" => "n12pmpajak",
28 | "domainName" => "myawesomelambda.example.com",
29 | "domainPrefix" => "myawesomelambda",
30 | "http" => {
31 | "method" => "GET",
32 | "path" => "/production/",
33 | "protocol" => "HTTP/1.1",
34 | "sourceIp" => "72.218.219.201",
35 | "userAgent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15"
36 | },
37 | "requestId" => "KSP7Mj94IAMEMFQ=",
38 | "routeKey" => "$default",
39 | "stage" => "production",
40 | "time" => "01/Apr/2020:02:18:09 +0000",
41 | "timeEpoch" => 1585707489142
42 | },
43 | "isBase64Encoded" => false
44 | }.freeze
45 |
46 | # Via CloudFront directly to API Gateway w/Origin Path.
47 | #
48 | # {"version"=>"2.0", "routeKey"=>"$default", "rawPath"=>"/production/", "rawQueryString"=>"colors[]=blue&colors[]=red", "headers"=>{"accept"=>"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "accept-encoding"=>"gzip", "content-length"=>"0", "host"=>"n12pmpajak.execute-api.us-east-1.amazonaws.com", "user-agent"=>"Amazon CloudFront", "via"=>"2.0 2f66aa06710fece8ed203ab0ea81eb56.cloudfront.net (CloudFront)", "x-amz-cf-id"=>"rQOlQ0dg3A9zrTfLqgBRWS8tDQJVmxtm3QrCf0wN0jEczCNucdJqKw==", "x-amzn-trace-id"=>"Root=1-5e83c9c2-b01dc280b06a4d00bcaeb480", "x-forwarded-for"=>"72.218.219.201, 3.231.2.1", "x-forwarded-host"=>"myawesomelambda.example.com", "x-forwarded-port"=>"443", "x-forwarded-proto"=>"https"}, "queryStringParameters"=>{"colors[]"=>"blue,red"}, "requestContext"=>{"accountId"=>nil, "apiId"=>"n12pmpajak", "domainName"=>"n12pmpajak.execute-api.us-east-1.amazonaws.com", "domainPrefix"=>"n12pmpajak", "http"=>{"method"=>"GET", "path"=>"/production/", "protocol"=>"HTTP/1.1", "sourceIp"=>" 3.231.2.1", "userAgent"=>"Amazon CloudFront"}, "requestId"=>"KRx2bgOfoAMETBA=", "routeKey"=>"$default", "stage"=>"production", "time"=>"31/Mar/2020:22:52:50 +0000", "timeEpoch"=>1585695170625}, "isBase64Encoded"=>false}
49 |
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/test/test_helper/events/http_v2_post.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module Events
3 | class HttpV2Post < Base
4 | # Via Custom Domain Name integration.
5 | #
6 | self.event = {
7 | "version" => "2.0",
8 | "routeKey" => "$default",
9 | "rawPath" => "/production/login",
10 | "rawQueryString" => "",
11 | "cookies" => ["signal1=test", "signal2=control"],
12 | "headers" => {
13 | "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
14 | "accept-encoding" => "gzip, deflate, br",
15 | "accept-language" => "en-us",
16 | "content-length" => "146",
17 | "content-type" => "application/x-www-form-urlencoded",
18 | "host" => "myawesomelambda.example.com",
19 | "origin" => "https://myawesomelambda.example.com",
20 | "referer" => "https://myawesomelambda.example.com/?colors[]=blue&colors[]=red",
21 | "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15",
22 | "x-amzn-trace-id" => "Root=1-5e852ca4-3a8dd0c91bfb6e85f0dc55ba",
23 | "x-forwarded-for" => "72.218.219.201",
24 | "x-forwarded-port" => "443",
25 | "x-forwarded-proto" => "https"
26 | },
27 | "requestContext" => {
28 | "accountId" => nil,
29 | "apiId" => "n12pmpajak",
30 | "domainName" => "myawesomelambda.example.com",
31 | "domainPrefix" => "myawesomelambda",
32 | "http" => {
33 | "method" => "POST",
34 | "path" => "/production/login",
35 | "protocol" => "HTTP/1.1",
36 | "sourceIp" => "72.218.219.201",
37 | "userAgent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15"
38 | },
39 | "requestId" => "KVPpsgCooAMEMBQ=",
40 | "routeKey" => "$default",
41 | "stage" => "production",
42 | "time" => "02/Apr/2020:00:07:00 +0000",
43 | "timeEpoch" => 1585786020373
44 | },
45 | "body" => "YXV0aGVudGljaXR5X3Rva2VuPThHR3ZFTTYlMkZiandERzNYQTNjaklaQk5Vc1RudWdTNXBjNEd1ZHlwNVpuZWNvcFFSNTYwcEt2bmJOcmhMYTRpVWpGT0NZT214JTJGeUV5SXBKcHNkM3NHQSUzRCUzRCZwYXNzd29yZD1wYXNzd29yZCZjb21taXQ9TG9naW4=",
46 | "isBase64Encoded" => true
47 | }
48 | end
49 | end
50 | end
--------------------------------------------------------------------------------
/test/test_helper/events/rest.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module Events
3 | class Rest < Base
4 | self.event = {
5 | "resource" => "/",
6 | "path" => "/",
7 | "httpMethod" => "GET",
8 | "headers" => {
9 | "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
10 | "Accept-Encoding" => "gzip",
11 | "Cookie" => "signal1=test; signal2=control",
12 | "Host" => "4o8v9z4feh.execute-api.us-east-1.amazonaws.com",
13 | "origin" => "https://myawesomelambda.example.com",
14 | "User-Agent" => "Amazon CloudFront",
15 | "Via" => "2.0 7f7e359e1c06a914d3d305785359b84d.cloudfront.net (CloudFront)",
16 | "X-Amz-Cf-Id" => "kXZzJ72NOsZSsPu-JzNUGyFei1G0r9uzoup3yHrwk4J5qGLKrdUrRA==",
17 | "X-Amzn-Trace-Id" => "Root=1-5e7fe714-fee6909429159440eb352c40",
18 | "X-Forwarded-For" => "72.218.219.201, 34.195.252.119",
19 | "X-Forwarded-Host" => "myawesomelambda.example.com",
20 | "X-Forwarded-Port" => "443",
21 | "X-Forwarded-Proto" => "https"
22 | },
23 | "multiValueHeaders" => {
24 | "Accept" => ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],
25 | "Accept-Encoding" => ["gzip"],
26 | "Cookie" => ["signal1=test; signal2=control"],
27 | "Host" => ["4o8v9z4feh.execute-api.us-east-1.amazonaws.com"],
28 | "origin" => ["https://myawesomelambda.example.com"],
29 | "User-Agent" => ["Amazon CloudFront"],
30 | "Via" => ["2.0 7f7e359e1c06a914d3d305785359b84d.cloudfront.net (CloudFront)"],
31 | "X-Amz-Cf-Id" => ["kXZzJ72NOsZSsPu-JzNUGyFei1G0r9uzoup3yHrwk4J5qGLKrdUrRA=="],
32 | "X-Amzn-Trace-Id" => ["Root=1-5e7fe714-fee6909429159440eb352c40"],
33 | "X-Forwarded-For" => ["72.218.219.201, 34.195.252.119"],
34 | "X-Forwarded-Host" => ["myawesomelambda.example.com"],
35 | "X-Forwarded-Port" => ["443"],
36 | "X-Forwarded-Proto" => ["https"]
37 | },
38 | "queryStringParameters" => { "colors[]" => "red" },
39 | "multiValueQueryStringParameters" => { "colors[]" => ["blue", "red"] },
40 | "pathParameters" => nil,
41 | "stageVariables" => nil,
42 | "requestContext" => {
43 | "resourceId" => "77ce0rz741",
44 | "resourcePath" => "/",
45 | "httpMethod" => "GET",
46 | "extendedRequestId" => "KIELPF7PoAMFePQ=",
47 | "requestTime" => "29/Mar/2020:00:08:52 +0000",
48 | "path" => "/production/",
49 | "accountId" => nil,
50 | "protocol" => "HTTP/1.1",
51 | "stage" => "production",
52 | "domainPrefix" => "4o8v9z4feh",
53 | "requestTimeEpoch" => 1585440532675,
54 | "requestId" => "e5c78607-6a25-4f15-bb87-aca7d4522093",
55 | "identity" => {
56 | "cognitoIdentityPoolId" => nil,
57 | "accountId" => nil,
58 | "cognitoIdentityId" => nil,
59 | "caller" => nil,
60 | "sourceIp" => "72.218.219.201",
61 | "principalOrgId" => nil,
62 | "accessKey" => nil,
63 | "cognitoAuthenticationType" => nil,
64 | "cognitoAuthenticationProvider" => nil,
65 | "userArn" => nil,
66 | "userAgent" => "Amazon CloudFront",
67 | "user" => nil
68 | },
69 | "domainName" => "4o8v9z4feh.execute-api.us-east-1.amazonaws.com",
70 | "apiId" => "4o8v9z4feh"
71 | },
72 | "body" => nil,
73 | "isBase64Encoded" => false
74 | }.freeze
75 | end
76 | end
77 | end
--------------------------------------------------------------------------------
/test/test_helper/events/rest_post.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module Events
3 | class RestPost < Base
4 |
5 | self.event = {
6 | "resource" => "/{resource+}",
7 | "path" => "/login",
8 | "httpMethod" => "POST",
9 | "headers" => {
10 | "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
11 | "Accept-Encoding" => "gzip, deflate, br",
12 | "content-type" => "application/x-www-form-urlencoded",
13 | "Cookie" => "signal1=test; signal2=control",
14 | "Host" => "4o8v9z4feh.execute-api.us-east-1.amazonaws.com",
15 | "origin" => "https://myawesomelambda.example.com",
16 | "User-Agent" => "Amazon CloudFront",
17 | "Via" => "2.0 7f7e359e1c06a914d3d305785359b84d.cloudfront.net (CloudFront)",
18 | "X-Amz-Cf-Id" => "HlVPz9T-9eYwLqzFi21O7EU7b_dvHEzqlgs4YLetEq036kBnyNv6_Q==",
19 | "X-Amzn-Trace-Id" => "Root=1-5e7fe714-d306a3db0536f45b4197aa52",
20 | "X-Forwarded-For" => "72.218.219.201, 70.132.33.68",
21 | "X-Forwarded-Host" => "myawesomelambda.example.com",
22 | "X-Forwarded-Port" => "443",
23 | "X-Forwarded-Proto" => "https"
24 | },
25 | "multiValueHeaders" => {
26 | "Accept" => ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],
27 | "Accept-Encoding" => ["gzip, deflate, br"],
28 | "content-type" => ["application/x-www-form-urlencoded"],
29 | "Cookie" => ["signal1=test; signal2=control"],
30 | "Host" => ["4o8v9z4feh.execute-api.us-east-1.amazonaws.com"],
31 | "origin" => ["https://myawesomelambda.example.com"],
32 | "User-Agent" => ["Amazon CloudFront"],
33 | "Via" => ["2.0 7f7e359e1c06a914d3d305785359b84d.cloudfront.net (CloudFront)"],
34 | "X-Amz-Cf-Id" => ["HlVPz9T-9eYwLqzFi21O7EU7b_dvHEzqlgs4YLetEq036kBnyNv6_Q=="],
35 | "X-Amzn-Trace-Id" => ["Root=1-5e7fe714-d306a3db0536f45b4197aa52"],
36 | "X-Forwarded-For" => ["72.218.219.201, 70.132.33.68"],
37 | "X-Forwarded-Host" => ["myawesomelambda.example.com"],
38 | "X-Forwarded-Port" => ["443"],
39 | "X-Forwarded-Proto" => ["https"]
40 | },
41 | "queryStringParameters" => nil,
42 | "multiValueQueryStringParameters" => nil,
43 | "pathParameters" => { "resource" => "login" },
44 | "stageVariables" => nil,
45 | "requestContext" => {
46 | "resourceId" => "s2mq69",
47 | "resourcePath" => "/{resource+}",
48 | "httpMethod" => "POST",
49 | "extendedRequestId" => "KIELOHygoAMFXxA=",
50 | "requestTime" => "29/Mar/2020:00:08:52 +0000",
51 | "path" => "/production/login",
52 | "accountId" => nil,
53 | "protocol" => "HTTP/1.1",
54 | "stage" => "production",
55 | "domainPrefix" => "4o8v9z4feh",
56 | "requestTimeEpoch" => 1585440532566,
57 | "requestId" => "552c9b8d-23fa-42de-9f89-9b56af1e6770",
58 | "identity" => {
59 | "cognitoIdentityPoolId" => nil,
60 | "accountId" => nil,
61 | "cognitoIdentityId" => nil,
62 | "caller" => nil,
63 | "sourceIp" => "72.218.219.201",
64 | "principalOrgId" => nil,
65 | "accessKey" => nil,
66 | "cognitoAuthenticationType" => nil,
67 | "cognitoAuthenticationProvider" => nil,
68 | "userArn" => nil,
69 | "userAgent" => "Amazon CloudFront",
70 | "user" => nil
71 | },
72 | "domainName" => "4o8v9z4feh.execute-api.us-east-1.amazonaws.com",
73 | "apiId" => "4o8v9z4feh"
74 | },
75 | "body" => "YXV0aGVudGljaXR5X3Rva2VuPXRObXZ0T2xlaHY0YU1GUlFlZTg4c2MxcTViZ3lJaGxqM3pscVJPelg5bWtyYnZwOVZrOUdoUlh4NG9sNFl4dW9jbDRJR2ZOOTk1ckh3SXhDSXhpVWZnJTNEJTNEJnBhc3N3b3JkPXBhc3N3b3JkJmNvbW1pdD1Mb2dpbg==",
76 | "isBase64Encoded" => true
77 | }.freeze
78 |
79 | end
80 | end
81 | end
--------------------------------------------------------------------------------
/test/test_helper/jobs_helpers.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module Jobs
3 | class BasicJob < ActiveJob::Base
4 | def perform(object)
5 | puts "BasicJob with: #{object.inspect}"
6 | end
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/test_helper/lambda_context.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | class LambdaContext
3 |
4 | RAW_DATA ={
5 | "clock_diff" => 1681486457423,
6 | "deadline_ms" => 1681492072985,
7 | "aws_request_id" => "d6f5961b-5034-4db5-b3a9-fa378133b0f0",
8 | "invoked_function_arn" => "arn:aws:lambda:us-east-1:576043675419:function:lamby-ws-production-WSConnectLambda-5in18cNskwz6",
9 | "log_group_name" => "/aws/lambda/lamby-ws-production-WSConnectLambda-5in18cNskwz6",
10 | "log_stream_name" => "2023/04/14/[$LATEST]55a1d458479a4546b64acca17af3a69f",
11 | "function_name" => "lamby-ws-production-WSConnectLambda-5in18cNskwz6",
12 | "memory_limit_in_mb" => "1792",
13 | "function_version" => "$LATEST"
14 | }.freeze
15 |
16 | def self.raw_data
17 | RAW_DATA.dup
18 | end
19 |
20 | def clock_diff
21 | 1585237646907
22 | end
23 |
24 | def deadline_ms
25 | 1585238452698
26 | end
27 |
28 | def aws_request_id
29 | 'a59284fd-d48c-4de5-af9e-df4254489ac2'
30 | end
31 |
32 | def invoked_function_arn
33 | 'arn:aws:lambda:us-east-1:123456789012:function:myawesomelambda'
34 | end
35 |
36 | def log_stream_name
37 | '2020/03/26[$LATEST]88b3605521bf4d7abfaa7bfa6dcd45f1'
38 | end
39 |
40 | def function_name
41 | 'myawesomelambda'
42 | end
43 |
44 | def memory_limit_in_mb
45 | '512'
46 | end
47 |
48 | def function_version
49 | '$LATEST'
50 | end
51 |
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/test/test_helper/lambdakiq_helpers.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module LambdakiqHelpers
3 |
4 | private
5 |
6 | def lambdakiq_client
7 | Lambdakiq.client.sqs
8 | end
9 |
10 | def lambdakiq_client_reset!
11 | Lambdakiq.instance_variable_set :@client, nil
12 | end
13 |
14 | def lambdakiq_client_stub_responses
15 | lambdakiq_client.stub_responses(:get_queue_url, {
16 | queue_url: 'https://sqs.us-stubbed-1.amazonaws.com'
17 | })
18 | redrive_policy = JSON.dump({maxReceiveCount: 8})
19 | lambdakiq_client.stub_responses(:get_queue_attributes, {
20 | attributes: { 'RedrivePolicy' => redrive_policy }
21 | })
22 | end
23 |
24 | def lambdakiq_api_requests
25 | lambdakiq_client.api_requests
26 | end
27 |
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/test/test_helper/stream_helpers.rb:
--------------------------------------------------------------------------------
1 | module TestHelpers
2 | module StreamHelpers
3 |
4 | private
5 |
6 | def silence_stream(stream)
7 | old_stream = stream.dup
8 | stream.reopen(IO::NULL)
9 | stream.sync = true
10 | yield
11 | ensure
12 | stream.reopen(old_stream)
13 | old_stream.close
14 | end
15 |
16 | def quietly
17 | silence_stream(STDOUT) do
18 | silence_stream(STDERR) do
19 | yield
20 | end
21 | end
22 | end
23 |
24 | def capture(stream)
25 | stream = stream.to_s
26 | captured_stream = Tempfile.new(stream)
27 | stream_io = eval("$#{stream}")
28 | origin_stream = stream_io.dup
29 | stream_io.reopen(captured_stream)
30 | yield
31 | stream_io.rewind
32 | return captured_stream.read
33 | ensure
34 | captured_stream.close
35 | captured_stream.unlink
36 | stream_io.reopen(origin_stream)
37 | end
38 |
39 | end
40 | end
41 |
--------------------------------------------------------------------------------