diff --git a/Gemfile b/Gemfile index a06f903..e19ae56 100644 --- a/Gemfile +++ b/Gemfile @@ -38,6 +38,8 @@ gem "jsonapi-serializer" gem "rolify" gem "pundit", "~> 2.4" +gem 'faraday' + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri windows ] @@ -45,6 +47,8 @@ group :development, :test do gem "rspec-rails" gem "shoulda-matchers" gem 'pundit-matchers', '~> 3.1' + gem 'webmock' + gem "vcr" end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 47544db..34f27a4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -78,6 +78,8 @@ GEM mutex_m securerandom (>= 0.3) tzinfo (~> 2.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) base64 (0.2.0) bcrypt (3.1.20) benchmark (0.3.0) @@ -88,6 +90,9 @@ GEM coderay (1.1.3) concurrent-ruby (1.3.4) connection_pool (2.4.1) + crack (1.0.0) + bigdecimal + rexml crass (1.0.6) date (3.4.0) debug (1.9.2) @@ -97,14 +102,22 @@ GEM docile (1.4.1) drb (2.2.1) erubi (1.13.0) + faraday (2.12.2) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.0) + net-http (>= 0.5.0) globalid (1.2.1) activesupport (>= 6.1) + hashdiff (1.1.2) i18n (1.14.6) concurrent-ruby (~> 1.0) io-console (0.7.2) irb (1.14.1) rdoc (>= 4.0.0) reline (>= 0.4.2) + json (2.10.1) jsonapi-serializer (2.2.0) activesupport (>= 4.2) jwt (2.9.3) @@ -124,6 +137,8 @@ GEM minitest (5.25.1) msgpack (1.7.3) mutex_m (0.2.0) + net-http (0.6.0) + uri net-imap (0.5.0) date net-protocol @@ -152,6 +167,7 @@ GEM method_source (~> 1.0) psych (5.2.0) stringio + public_suffix (6.0.1) puma (6.4.3) nio4r (~> 2.0) pundit (2.4.0) @@ -205,6 +221,7 @@ GEM psych (>= 4.0.0) reline (0.5.10) io-console (~> 0.5) + rexml (3.4.1) rolify (6.0.1) rspec-core (3.13.2) rspec-support (~> 3.13.0) @@ -237,6 +254,13 @@ GEM timeout (0.4.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + uri (1.0.2) + vcr (6.3.1) + base64 + webmock (3.25.0) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -254,6 +278,7 @@ DEPENDENCIES bcrypt (~> 3.1.7) bootsnap debug + faraday jsonapi-serializer jwt pg (~> 1.1) @@ -268,6 +293,8 @@ DEPENDENCIES shoulda-matchers simplecov tzinfo-data + vcr + webmock RUBY VERSION ruby 3.2.2p53 diff --git a/app/gateways/openai_gateway.rb b/app/gateways/openai_gateway.rb new file mode 100644 index 0000000..eef7b56 --- /dev/null +++ b/app/gateways/openai_gateway.rb @@ -0,0 +1,46 @@ +class OpenaiGateway + + def generate_interview_questions(description) + prompt = build_prompt(description) + + response = Faraday.post("https://api.openai.com/v1/chat/completions") do |req| + req.headers['Content-Type'] = 'application/json' + req.headers['Authorization'] = "Bearer #{Rails.application.credentials.dig(:open_ai, :key)}" + req.body = { + model: "gpt-4o-mini", + messages: [{ role: "user", content: prompt }], + max_tokens: 300, + temperature: 0.7 + }.to_json + end + + if response.status == 200 + api_response = JSON.parse(response.body, symbolize_names: true) + raw_content = api_response.dig(:choices, 0, :message, :content) + + cleaned_content = raw_content.match(/\[.*\]/m)&.to_s if raw_content + + if cleaned_content + { + success: true, + id: api_response[:id], + data: JSON.parse(cleaned_content) + } + else + { success: false, error: "Unexpected response format from OpenAI." } + end + else + { success: false, error: "Failed to fetch interview questions." } + end + rescue => error + { success: false, error: "An error occurred: #{error.message}" } + + end + + private + + def build_prompt(description) + "Please generate 10 practice interview questions based on the following job description: #{description}. + ONLY return a JSON array of strings containing the questions, with no additional formatting or object keys." + end +end \ No newline at end of file diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index 4e61d29..a439970 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -db0KsbB6Dkqw1Gu0zGbmiTGfLnwfgaOQBqtwvVwm9hZ9sykROJBhMpbz1XDbbUzAOq/bn73a5Fa49FCumPyMU2ujopiuw9n5wfaSfxCfVAN8aEySG9Ds5BAKw8LrnzPSnQlEGkFjZ4h2YySb9rtlpHE2/FoHEzpL1hxCnSy8TG1GLXediBXEMOHmGlvuLrS9uW+P01gXBoI1Mry+bqcCxLRpRR5nQrC8JOqlm5mehLxY9GHayFJLLVP304SUre6ZXkKQnf+m+CAHvARw1a6qj8459Cjg8ByAgVs4poimsiShxZTr9855kpVOysIQgjkUXfTfvsSgGKVghYrPKY+Ozf/LDhr0Pw8lqQdWUNnTOHkuQ1JTnTD7EXz4/O9fKJ34z/CUtXxF1PuIkLAxnHl8nsy1N6FI--TAyYW5qBzS/O/Mf2--O46I5K8a+Om4hNE1bSjn4A== \ No newline at end of file +ihxScc5DGq5kkzi/ZtqyFJVbq1hg/jQ07++IGNGuPuSBS9AXLlyr3ngqUP9sEegxGojrK7tOIqiGCxVWfLGfZ74XfAUK1LfNHqozQRYokzc+Q9b6rIjGZtAxVzxOqx/Jxhv596it9YzG4o8kBcbTsStPQaHPizi89Ccb24BlheZLVI1/nPL2asmjA/Ihsd1QDcQ3OBt5fg9/94Dj7fWHcrLRO+YQqWqgmBXraK/2LGOzaXClXS73LLDN6kpJNYgr0KNi7IW5+KEeREd1Fg9KpFDvDBEpaloP6GXyE5r2EGUHE/6imQskhJNWg6mHY/lCDFd9ySwl/SA8P5BvQtHx/2sJGXna9+fq6S7lrn0P46JevpV4bnzcla5UTcLbZ5637rd6XL1augKa316Qq3VLqrS+Dw0AnJ6YScI8xil5HxEO1IDaliE9bcC4dru0WD9o2CEL1MbCRTAFaitMSKh614bn+eHFmzHelW3FqKfhubHJXCUp+qGma7pDKIymVTPVg/M1q85kPprHBv1shdX4vARP4vmUaNWX/Xni2BrYXjgmD2F/B2BNtMEQz6cL56MOqw==--0fId3U/dHE/KKAka--SAhsQagzPr8bt/GIYu6YHg== \ No newline at end of file diff --git a/spec/cassettes/openai_gateway_failure.yml b/spec/cassettes/openai_gateway_failure.yml new file mode 100644 index 0000000..b59081b --- /dev/null +++ b/spec/cassettes/openai_gateway_failure.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"user\",\"content\":\"Please + generate 3 practice interview questions based on the following job description: + We're Hiring: Software Engineer (Remote)\\n\\n Tech Innovations Inc. + is looking for a skilled Software Engineer to join our growing team! \U0001F680 + If you're passionate about building scalable web applications and working + with cutting-edge technologies, we want to hear from you.\\n\\n In this + role, you'll collaborate with designers, product managers, and fellow engineers + to develop and maintain high-quality applications. You'll write clean, maintainable + code, participate in code reviews, and help troubleshoot and optimize performance..\\n + \ ONLY return a JSON array of strings containing the questions, with no + additional formatting or object keys.\"}],\"max_tokens\":300,\"temperature\":0.7}" + headers: + User-Agent: + - Faraday v2.12.2 + Content-Type: + - application/json + Authorization: + - Bearer + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 500 + message: OK + headers: + Date: + - Tue, 25 Feb 2025 22:33:19 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - turing-school-hmcjza + Openai-Processing-Ms: + - '766' + Openai-Version: + - '2020-10-01' + X-Ratelimit-Limit-Requests: + - '10000' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '9999' + X-Ratelimit-Remaining-Tokens: + - '199513' + X-Ratelimit-Reset-Requests: + - 8.64s + X-Ratelimit-Reset-Tokens: + - 145ms + X-Request-Id: + - req_80de3f6616c297bad6bd6e0023c44076 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - __cf_bm=6HsObrkGoTUHtlnBzDgSf8rDIOkfV1rUIbrqZBa0Rq8-1740522799-1.0.1.1-rytPdyc5lPUGENDMf7l3yc4PfvhAWrQB1u1ltrpDLYVFhY05HlMfNlUckjXUayQMNiLwD_wz7KJxdsl.0g4yPQ; + path=/; expires=Tue, 25-Feb-25 23:03:19 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=MetmYvmnLjtlW4KO5UUqgkoAln6n4mzSBMwlcQVPPd0-1740522799291-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - 917b2801cd435198-DEN + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-B4xmIRjD2i6UKGrMypwk9VDTwq30F", + "object": "chat.completion", + "created": 1740522798, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "[\n \"Can you describe your experience with building scalable web applications and the technologies you have used in your previous projects?\",\n \"How do you approach code reviews, and what specific practices do you follow to ensure code quality and maintainability?\",\n \"Can you provide an example of a time when you had to troubleshoot a performance issue in an application? What steps did you take to identify and resolve the problem?\"\n]", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 135, + "completion_tokens": 84, + "total_tokens": 219, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_06737a9306" + } + recorded_at: Tue, 25 Feb 2025 22:33:19 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/cassettes/openai_gateway_success.yml b/spec/cassettes/openai_gateway_success.yml new file mode 100644 index 0000000..bc3d214 --- /dev/null +++ b/spec/cassettes/openai_gateway_success.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"user\",\"content\":\"Please + generate 10 practice interview questions based on the following job description: + We're Hiring: Software Engineer (Remote)\\n\\n Tech Innovations Inc. is + looking for a skilled Software Engineer to join our growing team! \U0001F680 + If you're passionate about building scalable web applications and working + with cutting-edge technologies, we want to hear from you.\\n\\n In this + role, you'll collaborate with designers, product managers, and fellow engineers + to develop and maintain high-quality applications. You'll write clean, maintainable + code, participate in code reviews, and help troubleshoot and optimize performance..\\n + \ ONLY return a JSON array of strings containing the questions, with no + additional formatting or object keys.\"}],\"max_tokens\":300,\"temperature\":0.7}" + headers: + User-Agent: + - Faraday v2.12.2 + Content-Type: + - application/json + Authorization: + - Bearer + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 26 Feb 2025 16:33:08 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - turing-school-hmcjza + Openai-Processing-Ms: + - '3386' + Openai-Version: + - '2020-10-01' + X-Ratelimit-Limit-Requests: + - '10000' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '9999' + X-Ratelimit-Remaining-Tokens: + - '199515' + X-Ratelimit-Reset-Requests: + - 8.64s + X-Ratelimit-Reset-Tokens: + - 145ms + X-Request-Id: + - req_a1933adba487b4d449c73aaf598f8dc9 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - __cf_bm=7QBZTDfRS2Y_W5LkWTLDqYmVNlsl3R7BekIz5orUfSs-1740587588-1.0.1.1-rzrxc7eU7sN5ZhBgBHVmFASXqzOWctLCFmzZAg8KPRs45afl4TaLH5tabkvqm4VlbOKaGEnuKvh6jRjxjB5icg; + path=/; expires=Wed, 26-Feb-25 17:03:08 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=7FZj7kFinmoExtpsWu9jjERdwoTo7USkDOtXcuIT4tk-1740587588353-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - 918155b1bc5c51e8-DEN + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-B5EdFZ1ZskaRhS9UcrnAkdN2DxCO8", + "object": "chat.completion", + "created": 1740587585, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "[\n \"Can you describe your experience with building scalable web applications?\",\n \"What programming languages and frameworks do you feel most comfortable working with, and why?\",\n \"How do you approach writing clean and maintainable code?\",\n \"Can you give an example of a time you collaborated with designers or product managers on a project?\",\n \"What strategies do you use to troubleshoot and optimize application performance?\",\n \"How do you handle code reviews, and what do you think is important in providing feedback?\",\n \"Can you discuss a challenging technical problem you've encountered and how you resolved it?\",\n \"What tools or methodologies do you use to ensure the quality of your code?\",\n \"How do you stay current with emerging technologies and trends in software development?\",\n \"What do you think is the most important aspect of working in a remote team environment?\"\n]", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 135, + "completion_tokens": 171, + "total_tokens": 306, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_06737a9306" + } + recorded_at: Wed, 26 Feb 2025 16:33:08 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/cassettes/openai_gateway_success_formatting_issue.yml b/spec/cassettes/openai_gateway_success_formatting_issue.yml new file mode 100644 index 0000000..7a8d7c4 --- /dev/null +++ b/spec/cassettes/openai_gateway_success_formatting_issue.yml @@ -0,0 +1,122 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.openai.com/v1/chat/completions + body: + encoding: UTF-8 + string: "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"user\",\"content\":\"Please + generate 3 practice interview questions based on the following job description: + We're Hiring: Software Engineer (Remote)\\n\\n Tech Innovations Inc. + is looking for a skilled Software Engineer to join our growing team! \U0001F680 + If you're passionate about building scalable web applications and working + with cutting-edge technologies, we want to hear from you.\\n\\n In this + role, you'll collaborate with designers, product managers, and fellow engineers + to develop and maintain high-quality applications. You'll write clean, maintainable + code, participate in code reviews, and help troubleshoot and optimize performance..\\n + \ ONLY return a JSON array of strings containing the questions, with no + additional formatting or object keys.\"}],\"max_tokens\":300,\"temperature\":0.7}" + headers: + User-Agent: + - Faraday v2.12.2 + Content-Type: + - application/json + Authorization: + - Bearer + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 25 Feb 2025 22:38:36 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Access-Control-Expose-Headers: + - X-Request-ID + Openai-Organization: + - turing-school-hmcjza + Openai-Processing-Ms: + - '1319' + Openai-Version: + - '2020-10-01' + X-Ratelimit-Limit-Requests: + - '10000' + X-Ratelimit-Limit-Tokens: + - '200000' + X-Ratelimit-Remaining-Requests: + - '9999' + X-Ratelimit-Remaining-Tokens: + - '199513' + X-Ratelimit-Reset-Requests: + - 8.64s + X-Ratelimit-Reset-Tokens: + - 145ms + X-Request-Id: + - req_1d9fbe7f59b820c167026aaaa2fed864 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - __cf_bm=W3KMKXuq2LvTR.LfT2KUYrwnCB_qxjwsj.k5yNr0Ur0-1740523116-1.0.1.1-k4WWkh9ndYoseh.rbAiPscMYpzoE_rk9rD3..rQ2CBgAGhbU99uBLQSC854o7ETxFlT_09TmOBGs4jn0Kzrz8w; + path=/; expires=Tue, 25-Feb-25 23:08:36 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=3jAFSKZe0DPi3LbSBYydyCzOfSDZDveOSu9wzcqr1No-1740523116906-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + X-Content-Type-Options: + - nosniff + Server: + - cloudflare + Cf-Ray: + - 917b2fa5cbe35198-DEN + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: | + { + "id": "chatcmpl-B4xrP9mOz2XiJiq2Whq4oaguKx7xZ", + "object": "chat.completion", + "created": 1740523115, + "model": "gpt-4o-mini-2024-07-18", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "I'm not an array", + "refusal": null + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 135, + "completion_tokens": 80, + "total_tokens": 215, + "prompt_tokens_details": { + "cached_tokens": 0, + "audio_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 0, + "audio_tokens": 0, + "accepted_prediction_tokens": 0, + "rejected_prediction_tokens": 0 + } + }, + "service_tier": "default", + "system_fingerprint": "fp_06737a9306" + } + recorded_at: Tue, 25 Feb 2025 22:38:36 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/gateways/openai_gateway_spec.rb b/spec/gateways/openai_gateway_spec.rb new file mode 100644 index 0000000..37fce3f --- /dev/null +++ b/spec/gateways/openai_gateway_spec.rb @@ -0,0 +1,57 @@ +require 'rails_helper' + +RSpec.describe OpenaiGateway do + before :each do + @gateway = OpenaiGateway.new + @valid_description = "We're Hiring: Software Engineer (Remote) + + Tech Innovations Inc. is looking for a skilled Software Engineer to join our growing team! 🚀 If you're passionate about building scalable web applications and working with cutting-edge technologies, we want to hear from you. + + In this role, you'll collaborate with designers, product managers, and fellow engineers to develop and maintain high-quality applications. You'll write clean, maintainable code, participate in code reviews, and help troubleshoot and optimize performance." + + end + + describe 'generate_interview_questions' do + it 'returns a JSON response with an array of interview questions from OpenAI based on a job description' do + + VCR.use_cassette("openai_gateway_success") do + gateway_response = @gateway.generate_interview_questions(@valid_description) + expect(gateway_response[:success]).to eq(true) + expect(gateway_response[:id]).to be_present + expect(gateway_response[:data].size).to eq(10) + expect(gateway_response[:data][0]).to eq("Can you describe your experience with building scalable web applications?") + expect(gateway_response[:data][1]).to eq("What programming languages and frameworks do you feel most comfortable working with, and why?") + expect(gateway_response[:data][2]).to eq("How do you approach writing clean and maintainable code?") + end + end + + it 'can handle openai returning an error' do + + VCR.use_cassette("openai_gateway_failure") do + gateway_response = @gateway.generate_interview_questions(@valid_description) + expect(gateway_response[:success]).to eq(false) + expect(gateway_response[:error]).to eq("Failed to fetch interview questions.") + end + end + + it 'can handle openai returning an unexpected format' do + + VCR.use_cassette("openai_gateway_success_formatting_issue") do + gateway_response = @gateway.generate_interview_questions(@valid_description) + expect(gateway_response[:success]).to eq(false) + expect(gateway_response[:error]).to eq("Unexpected response format from OpenAI.") + + end + end + + it 'can handle openai returning an unexpected error' do + allow(Faraday).to receive(:post).and_raise(StandardError.new("Something went wrong")) + + response = @gateway.generate_interview_questions(@valid_description) + + expect(response[:success]).to eq(false) + expect(response[:error]).to include("An error occurred: Something went wrong") + end + + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1053d92..b20192d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,16 @@ # it. # # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +require 'webmock' +require 'vcr' + +VCR.configure do |c| + c.cassette_library_dir = "spec/cassettes" + c.hook_into :webmock + c.configure_rspec_metadata! + c.filter_sensitive_data('') { Rails.application.credentials.dig(:open_ai, :key) } +end + RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest