Skip to content

Commit 6017043

Browse files
authored
feat(fxelasticsearch): Provided module (#37)
* feat(fxelasticsearch): add fxelasticsearch module * feat(fxelasticsearch): remove cloud_id and api_key auth for now and add general elasticsearch mock * feat(fxelasticsearch): add more tests * feat(fxelasticsearch): remove redundant example method * feat(fxelasticsearch): add mock elastic search client with http transport-level mocking capabilities * feat(fxelasticsearch): remove fxelasticsearch in release please config as handled by bot * feat(fxelasticsearch): remove unused fx.lifecyle * feat(fxelasticsearch): move mocks in fxelasticsearchtest folder * feat(fxelasticsearch): add empty line end of file * feat(fxelasticsearch): add module name in README * feat(fxelasticsearch): add module name in config
1 parent 7438d62 commit 6017043

File tree

16 files changed

+918
-0
lines changed

16 files changed

+918
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: "fxelasticsearch-ci"
2+
3+
on:
4+
push:
5+
branches:
6+
- "feat**"
7+
- "fix**"
8+
- "hotfix**"
9+
- "chore**"
10+
paths:
11+
- "fxelasticsearch/**.go"
12+
- "fxelasticsearch/go.mod"
13+
- "fxelasticsearch/go.sum"
14+
pull_request:
15+
types:
16+
- opened
17+
- synchronize
18+
- reopened
19+
branches:
20+
- main
21+
paths:
22+
- "fxelasticsearch/**.go"
23+
- "fxelasticsearch/go.mod"
24+
- "fxelasticsearch/go.sum"
25+
26+
jobs:
27+
ci:
28+
uses: ./.github/workflows/common-ci.yml
29+
secrets: inherit
30+
with:
31+
module: "fxelasticsearch"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
| Module | Description |
1212
|------------------------------------|--------------------------------------------------------------------------|
13+
| [fxelasticsearch](fxelasticsearch) | Module for [Elasticsearch](https://www.elastic.co/elasticsearch/) |
1314
| [fxgcppubsub](fxgcppubsub) | Module for [GCP Pub/Sub](https://cloud.google.com/pubsub) |
1415
| [fxgomysqlserver](fxgomysqlserver) | Module for [Go Mysql Server](https://github.com/dolthub/go-mysql-server) |
1516
| [fxjsonapi](fxjsonapi) | Module for [JSON API](https://github.com/google/jsonapi) |

fxelasticsearch/.golangci.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
run:
2+
timeout: 5m
3+
concurrency: 8
4+
5+
linters:
6+
enable:
7+
- asasalint
8+
- asciicheck
9+
- bidichk
10+
- bodyclose
11+
- containedctx
12+
- contextcheck
13+
- cyclop
14+
- decorder
15+
- dogsled
16+
- durationcheck
17+
- errcheck
18+
- errchkjson
19+
- errname
20+
- errorlint
21+
- exhaustive
22+
- forbidigo
23+
- forcetypeassert
24+
- gocognit
25+
- goconst
26+
- gocritic
27+
- gocyclo
28+
- godot
29+
- godox
30+
- gofmt
31+
- goheader
32+
- gomoddirectives
33+
- gomodguard
34+
- goprintffuncname
35+
- gosec
36+
- gosimple
37+
- govet
38+
- grouper
39+
- importas
40+
- ineffassign
41+
- interfacebloat
42+
- logrlint
43+
- maintidx
44+
- makezero
45+
- misspell
46+
- nestif
47+
- nilerr
48+
- nilnil
49+
- nlreturn
50+
- nolintlint
51+
- nosprintfhostport
52+
- prealloc
53+
- predeclared
54+
- promlinter
55+
- reassign
56+
- staticcheck
57+
- tenv
58+
- thelper
59+
- tparallel
60+
- typecheck
61+
- unconvert
62+
- unparam
63+
- unused
64+
- usestdlibvars
65+
- whitespace

fxelasticsearch/README.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Fx Elasticsearch Module
2+
3+
[![ci](https://github.com/ankorstore/yokai-contrib/actions/workflows/fxelasticsearch-ci.yml/badge.svg)](https://github.com/ankorstore/yokai-contrib/actions/workflows/fxelasticsearch-ci.yml)
4+
[![go report](https://goreportcard.com/badge/github.com/ankorstore/yokai-contrib/fxelasticsearch)](https://goreportcard.com/report/github.com/ankorstore/yokai-contrib/fxelasticsearch)
5+
[![codecov](https://codecov.io/gh/ankorstore/yokai-contrib/graph/badge.svg?token=ghUBlFsjhR&flag=fxelasticsearch)](https://codecov.io/gh/ankorstore/yokai-contrib/fxelasticsearch)
6+
[![Deps](https://img.shields.io/badge/osi-deps-blue)](https://deps.dev/go/github.com%2Fankorstore%2Fyokai-contrib%2Ffxelasticsearch)
7+
[![PkgGoDev](https://pkg.go.dev/badge/github.com/ankorstore/yokai-contrib/fxelasticsearch)](https://pkg.go.dev/github.com/ankorstore/yokai-contrib/fxelasticsearch)
8+
9+
> [Fx](https://uber-go.github.io/fx/) module for [Elasticsearch](https://github.com/elastic/go-elasticsearch).
10+
11+
<!-- TOC -->
12+
* [Overview](#overview)
13+
* [Installation](#installation)
14+
* [Configuration](#configuration)
15+
* [Testing](#testing)
16+
<!-- TOC -->
17+
18+
## Overview
19+
20+
This module provides to your Fx application an [elasticsearch.Client](https://pkg.go.dev/github.com/elastic/go-elasticsearch/v8),
21+
that you can `inject` anywhere to interact with [Elasticsearch](https://github.com/elastic/go-elasticsearch).
22+
23+
## Installation
24+
25+
Install the module:
26+
27+
```shell
28+
go get github.com/ankorstore/yokai-contrib/fxelasticsearch
29+
```
30+
31+
Then activate it in your application bootstrapper:
32+
33+
```go
34+
// internal/bootstrap.go
35+
package internal
36+
37+
import (
38+
"github.com/ankorstore/yokai-contrib/fxelasticsearch"
39+
"github.com/ankorstore/yokai/fxcore"
40+
)
41+
42+
var Bootstrapper = fxcore.NewBootstrapper().WithOptions(
43+
// load modules
44+
fxelasticsearch.FxElasticsearchModule,
45+
// ...
46+
)
47+
```
48+
49+
## Configuration
50+
51+
Configuration reference:
52+
53+
```yaml
54+
# ./configs/config.yaml
55+
app:
56+
name: app
57+
env: dev
58+
version: 0.1.0
59+
debug: true
60+
modules:
61+
elasticsearch:
62+
address: ${ELASTICSEARCH_ADDRESS}
63+
username: ${ELASTICSEARCH_USERNAME}
64+
password: ${ELASTICSEARCH_PASSWORD}
65+
```
66+
67+
Notes:
68+
- The `modules.elasticsearch.address` configuration key is mandatory
69+
- The `modules.elasticsearch.username` and `modules.elasticsearch.password` configuration keys are optional
70+
- See [Elasticsearch configuration](https://pkg.go.dev/github.com/elastic/go-elasticsearch/v8#Config) documentation for more details
71+
72+
## Testing
73+
74+
In `test` mode, an additional mock Elasticsearch client is provided with HTTP transport-level mocking capabilities.
75+
76+
### Automatic Test Environment Support
77+
78+
When `APP_ENV=test`, the module automatically provides a default mock Elasticsearch client that returns empty successful responses. This allows your application to start and run basic tests without any additional setup.
79+
80+
### Custom Mock Clients
81+
82+
For specific test scenarios, you can create custom mock clients with controlled responses:
83+
84+
```go
85+
package service_test
86+
87+
import (
88+
"context"
89+
"errors"
90+
"strings"
91+
"testing"
92+
93+
"github.com/ankorstore/yokai-contrib/fxelasticsearch/fxelasticsearchtest"
94+
"github.com/stretchr/testify/assert"
95+
)
96+
97+
func TestMyService_Search(t *testing.T) {
98+
// Define mock response
99+
mockResponse := `{
100+
"took": 5,
101+
"timed_out": false,
102+
"hits": {
103+
"total": {"value": 1},
104+
"hits": [
105+
{
106+
"_source": {"title": "Test Document", "content": "Test content"}
107+
}
108+
]
109+
}
110+
}`
111+
112+
// Create mock Elasticsearch client
113+
esClient, err := fxelasticsearchtest.NewMockESClientWithSingleResponse(mockResponse, 200)
114+
assert.NoError(t, err)
115+
116+
// Use the mock client in your service
117+
service := NewMyService(esClient)
118+
119+
// Test your service methods that use Elasticsearch
120+
results, err := service.Search(context.Background(), "test-index", "test query")
121+
assert.NoError(t, err)
122+
assert.Len(t, results, 1)
123+
assert.Equal(t, "Test Document", results[0]["title"])
124+
}
125+
126+
### Using Injected Mock Client in Tests
127+
128+
You can also use the automatically provided mock client in Fx-based tests:
129+
130+
```go
131+
func TestWithFxInjection(t *testing.T) {
132+
t.Setenv("APP_ENV", "test")
133+
134+
var esClient *elasticsearch.Client
135+
136+
app := fxtest.New(
137+
t,
138+
fxconfig.FxConfigModule,
139+
fxelasticsearch.FxElasticsearchModule,
140+
fx.Populate(&esClient),
141+
)
142+
143+
app.RequireStart()
144+
145+
// The injected client is a mock that returns empty successful responses
146+
res, err := esClient.Search()
147+
assert.NoError(t, err)
148+
assert.Equal(t, 200, res.StatusCode)
149+
150+
app.RequireStop()
151+
}
152+
```
153+
154+
See [example](module_test.go).

fxelasticsearch/factory.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package fxelasticsearch
2+
3+
import (
4+
"github.com/ankorstore/yokai/config"
5+
"github.com/elastic/go-elasticsearch/v8"
6+
)
7+
8+
var _ ElasticsearchClientFactory = (*DefaultElasticsearchClientFactory)(nil)
9+
10+
type ElasticsearchClientFactory interface {
11+
Create() (*elasticsearch.Client, error)
12+
}
13+
14+
type DefaultElasticsearchClientFactory struct {
15+
config *config.Config
16+
}
17+
18+
func NewDefaultElasticsearchClientFactory(config *config.Config) *DefaultElasticsearchClientFactory {
19+
return &DefaultElasticsearchClientFactory{
20+
config: config,
21+
}
22+
}
23+
24+
func (f *DefaultElasticsearchClientFactory) Create() (*elasticsearch.Client, error) {
25+
// Create Elasticsearch config
26+
cfg := elasticsearch.Config{
27+
Addresses: []string{f.config.GetString("modules.elasticsearch.address")},
28+
}
29+
30+
// Add authentication if provided
31+
if f.config.IsSet("modules.elasticsearch.username") && f.config.IsSet("modules.elasticsearch.password") {
32+
cfg.Username = f.config.GetString("modules.elasticsearch.username")
33+
cfg.Password = f.config.GetString("modules.elasticsearch.password")
34+
}
35+
36+
return elasticsearch.NewClient(cfg)
37+
}

fxelasticsearch/factory_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package fxelasticsearch_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/ankorstore/yokai-contrib/fxelasticsearch"
7+
"github.com/ankorstore/yokai/config"
8+
"github.com/elastic/go-elasticsearch/v8"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestDefaultElasticsearchClientFactory(t *testing.T) {
13+
createConfig := func() (*config.Config, error) {
14+
return config.NewDefaultConfigFactory().Create(
15+
config.WithFilePaths("./testdata/config"),
16+
)
17+
}
18+
19+
t.Run("create success", func(t *testing.T) {
20+
t.Setenv("ELASTICSEARCH_ADDRESS", "http://localhost:9200")
21+
22+
cfg, err := createConfig()
23+
assert.NoError(t, err)
24+
25+
factory := fxelasticsearch.NewDefaultElasticsearchClientFactory(cfg)
26+
27+
client, err := factory.Create()
28+
assert.NoError(t, err)
29+
assert.IsType(t, &elasticsearch.Client{}, client)
30+
})
31+
32+
t.Run("create with auth", func(t *testing.T) {
33+
t.Setenv("ELASTICSEARCH_ADDRESS", "http://localhost:9200")
34+
t.Setenv("ELASTICSEARCH_USERNAME", "elastic")
35+
t.Setenv("ELASTICSEARCH_PASSWORD", "changeme")
36+
37+
cfg, err := createConfig()
38+
assert.NoError(t, err)
39+
40+
factory := fxelasticsearch.NewDefaultElasticsearchClientFactory(cfg)
41+
42+
client, err := factory.Create()
43+
assert.NoError(t, err)
44+
assert.IsType(t, &elasticsearch.Client{}, client)
45+
})
46+
}

0 commit comments

Comments
 (0)