├── .gitignore
├── .travis.yml
├── Makefile
├── README.md
├── architecture.PNG
├── architecture.xml
├── build.sh
├── cmd
├── cert.pem
├── config.json
└── key.pem
├── db
├── initFromJsons.go
└── replayDB.go
├── getversion.sh
├── main.go
├── middleware
├── middlehandler.go
├── middleware.go
├── pactgenerate.go
└── test.json
├── offline
├── logger.go
├── modals.go
├── repo.go
├── repo_test.go
├── sample_handler.go
└── sample_routes_define.go
├── online
└── proxyHandler.go
├── utils
├── helper.go
├── helper_test.go
└── jsonrelated.go
├── version.go
└── workflow.PNG
/.gitignore:
--------------------------------------------------------------------------------
1 | MockXServer/cmd*
2 | MockXServer/pacts/
3 | MockXServer/*DB
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | script: sh ./build.sh
3 | notifications:
4 | email: false
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | default:build
2 | build:
3 | @bash getversion.sh
4 | @bash build.sh
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MockXServer
2 | [](https://travis-ci.org/compasses/MockXServer)
3 | ## Release
4 |
5 | ## 说明
6 | Auto-record/ Auto-replay API请求;支持oneline 和 offline 两种工作模式,支持http和https方式。
7 | RunMode为online时,作用相当于一个API Proxy,并自动记录请求/响应记录到一个文件数据库:ReplayDB;
8 | RunMode为offline时,作用类似于MockServer,默认从ReplayDB中请求响应,如果找不到对应的响应,然后会根据OfflineHandler配置项是否从offline handler中请求对应的响应;
9 | 使用场景常在开发、测试中使用,因能够自动Record和Replay会节省很多准备真实环境的时间。
10 | 特别在两个团队分前后台开发的时候,把后台服务直接replay或通过offline handler simulate出来,两个团队之间只进行API接口编程,有助于提升开发效率。
11 |
12 | ## PACT record & Json 文件生成
13 | 1. 通过请求http(s)://MockXServer_IP:Port/pact生成pact文件,目前只支持1.1 的版本
14 | 2. 通过请求http(s)://MockXServer_IP:Port/json生成json文件,里面记录了当前record的所有请求/响应记录。
15 |
16 | ## offline
17 | 1. RESTFul资源服务器,作为离线使用需要完成正常的所有功能。
18 | 2. 自带存储,需要存储可能重复使用的信息。保证功能的完备性
19 |
20 | ## online
21 | 1. 类似API的proxy,直接将请求路由给目的服务器。
22 | 2. 目前是主要记录请求和响应信息,http相关的能够完整的记录下来。
23 | 3. 支持https,所以这个proxy可以将请求做二次处理,并能记录详细信息。
24 |
25 | ## config.json说明
26 | 1. RunMode, 表示offline和online;
27 | 2. TLS,是否起用https模式;
28 | 3. RemoteServer,online时,请求的远程服务器;
29 | 4. ListenOn, APIService的监听地址;
30 | 5. OfflineHandler, 当runMode为offline时,是否启用offline handler响应请求;
31 | 5. LogFile, 日志文件名字。留空的话直接打印到命令行窗口。
32 |
33 | ## 运行
34 | 在 Mac或Linux 上执行make 即可,在windows上执行 *sh buil.sh*。可执行文件位于cmd目录下。
35 |
36 | ## 简单使用说明
37 | 1. 打开online模式,运行mock server,会将请求、响应消息信息保存到ReplayDB文件中;
38 | 2. 分享给其他开发者ReplayDB,只需配置为offline模式,所有online模式中记录的API都能正常的响应。
39 |
40 | ### example
41 | 一个Get请求的记录:
42 | ```
43 | "/api/CreditCards/v1/getCardsByCustomerId/120016694099968": {
44 | "GET": [
45 | {
46 | "request": null,
47 | "response": {
48 | "200": "[{\"id\":120041944596480,\"last4Digits\":\"0005\",\"cardType\":\"AMERICANEXPRESS\",\"nameOnCard\":\"Jet 1\",\"expiryYear\":\"2222\",\"expiryMonth\":\"12\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null},{\"id\":120043117346816,\"last4Digits\":\"5904\",\"cardType\":\"DINERSCLUB\",\"nameOnCard\":\"Jet 2\",\"expiryYear\":\"2222\",\"expiryMonth\":\"12\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null},{\"id\":120045496467456,\"last4Digits\":\"1117\",\"cardType\":\"DISCOVER\",\"nameOnCard\":\"Jet 3\",\"expiryYear\":\"2222\",\"expiryMonth\":\"12\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null},{\"id\":120048522469376,\"last4Digits\":\"4444\",\"cardType\":\"MASTERCARD\",\"nameOnCard\":\"Jet 4\",\"expiryYear\":\"2322\",\"expiryMonth\":\"12\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null},{\"id\":120049814609920,\"last4Digits\":\"5100\",\"cardType\":\"MASTERCARD\",\"nameOnCard\":\"Jet 5\",\"expiryYear\":\"2323\",\"expiryMonth\":\"11\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null}]"
49 | }
50 | }
51 | ]
52 | }
53 | ```
54 |
55 | 两次Post请求的记录:
56 | ```
57 | "/api/CreditCardCheckout/v1/checkout": {
58 | "POST": [
59 | {
60 | "request": {
61 | "amount": 100,
62 | "currencyCode": "USD",
63 | "id": 118650906992640,
64 | "paymentAccountId": 118628324237312
65 | },
66 | "response": {
67 | "200": "{\"pnref\":\"A10AA261D8C8\",\"paymentResponse\":{\"requestId\":\"FE60350123E15D3A8DF6D44BE67110CF\",\"result\":0,\"respMsg\":\"Approved\",\"status\":true,\"authCode\":\"040PNI\",\"avsAddr\":null,\"avsZip\":null,\"preFpsMsg\":null,\"postFpsMsg\":null,\"transError\":null,\"pnref\":\"A10AA261D8C8\"}}"
68 | }
69 | },
70 | {
71 | "request": {
72 | "amount": 222,
73 | "currencyCode": "USD",
74 | "id": 118650906992640,
75 | "paymentAccountId": 118628324237312
76 | },
77 | "response": {
78 | "200": "{\"pnref\":\"A70AA0C6F56C\",\"paymentResponse\":{\"requestId\":\"62B4918913F4A4FE7BB826888BC7B29A\",\"result\":0,\"respMsg\":\"Approved\",\"status\":true,\"authCode\":\"537PNI\",\"avsAddr\":null,\"avsZip\":null,\"preFpsMsg\":null,\"postFpsMsg\":null,\"transError\":null,\"pnref\":\"A70AA0C6F56C\"}}"
79 | }
80 | }
81 | ]
82 | }
83 | ```
84 | 上面的结果是通过访问**http(s)://MockXServer_IP:Port/json**获得的json记录。
85 |
86 |
87 | ## 结构图:
88 | 
89 |
90 | 整体结构较为简单,MiddleWare层封装了offline和online。offline和online会各自访问DB进行读写。
91 |
92 | ### Third party lib
93 | 1. [httprouter](http://godoc.org/github.com/julienschmidt/httprouter)
94 | 2. [boltDB](http://godoc.org/github.com/boltdb/bolt)
95 | 3. [PACT-go](https://github.com/SEEK-Jobs/pact-go)
96 |
--------------------------------------------------------------------------------
/architecture.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/compasses/MockXServer/1169543ae7d372309fc9dbbdf9aaba32a7b95c91/architecture.PNG
--------------------------------------------------------------------------------
/architecture.xml:
--------------------------------------------------------------------------------
1 | 7Vxdc6LMEv41qTrvRSwQMOYyms3uqbf2nFSSevecSyKjUouMC5iP/fXbDd04w2CiQqIxeqEywOD00/30x8x44gxnT18Tfz79LgMRnXSt4OnEuTzpdm3XceADW56LlvO+XTRMkjAomqxlw234W9Cd3LoIA5FSW9GUSRll4VxvHMk4FqNMa/OTRD7ql41lRE+lhrk/4ScuG25HfmS2/giDbFq09ru9Zfs3EU6m/GS7d16cufdHPyeJXMT0vJOuM85fxemZz33RQNOpH8hHpcn5AnJNpISe8dvsaSgilK0utqsVZ8vfnYiYftvLN8ApvOHBjxY09P+Ox1EYC/p52TOL5HEaZuJ27o/w+BFgP3EG02wWwZENX9MskT/FUEYyya93rq4ceMGZcRhF3B5L6NkZTBI/COEXVprHMs5IF+weHWs9WvCCdj8KJzG0jaALAScH5qhJEA8iyQRrZSleUFshZyJLnuESPntOt5DGdnskmscl/rZNKE8V7HuEpE8qNym7XoodvpDk61Ega1FRiD8jCJ5FBrYLEDwDBEP8YK1z/JqG8SQSF0g0MPAgTICDQonSSOUCf+hgU6BQqMPhxwHKJYETUHafgFOAcugSFaczEnITnOhJCk43Yh75z5c0sBq8Rs9gTUE++AMHxrWJSwgYzyFrUYBhptOA6TcH5myVLzkik4c2Ord5LsnrFWSYApsgQ+BqJuPjBRfX/4b3W5HAOFZjtLhH4R06pXk6pdVZThkvqQD1W/A99CgFoAKZoY+X9SJ4FLiZB/g6wa/cdA+g6S3wIO26CqQgC2xXYEtEGv727/MLULRzGcZZPhJvcOJdorAXmYRrclAU2UdijF2hgEMInC+oOZOoFymoCfjIOzy4PHU3gLYNJHuVKMIi/VeQ7FGTCqTTAgdyAnOMItYAqusQBZZRBIUH7xBF2PxrjybXBpJsT7swOTOHNUAQcUB2djmK/DQNR2snTi8LsbQ48RRm/0NAO6CdxeH/qfOV8gUzT3LTXibimZ9MBHv+okkEWq3CxECRMfswVcbclojIz8IHvcJRJ3h6wjWqpRq/aAhXM65iLHSPWnCodMP3sclXuinGb3ST60A55PXUwkyq7Q4c3+Uy+wpC7lo3Ip3LOIU8u87e/RnaVXyf4sc4kbP8Dk419NNvSQZV5q3a/ywMArx/EPn3IhqUFShFTakGtb46JzIDZcmdkYf6XKPDbHZVkiircjQqrbJVRx6nVsf27AqBNNNZ1i79Bjkep4h7M71yDb36W8C9F9B2jeW9LnRhfRfZVAbF9xvxayHSbCChGmoq2j8QiOc3szKq122sTjqRsY83o4FXXEqpUnURiISrx1FOplO4TsAN7+lrnAoT2ZaZSNU6G7eNsGGNKtGbOZtSiiC85Bm9DXaXH6CvUVxP4Yl6FoYB7IqsjgUupTi+FkkIY0c6yVlnLRdF/kh1UTT4PXFRbk+PJz2OL/fXSZnFrC46qRuRLZJ4Mw8VwhwDzHTg1MPnc0Zslo2dkdVxbU7HCf5TiiS21c523Y9ZZdslAZUHBcNgHyX/aNxzDj+8AffUhMckiGN4vJ0emTVBB5nnPzDz2LWuChqxiqi3PtRVqEWJig+VeGyAor5IU6O/bKNthMae1de92mnD2Ji7rhRfeAanXbYyC5u7yM1LturALJ9CWHn4tEzcofyuUpaTO5utKYvEu7/h0kejLF4zoSiTqwVLaTibg1Ztk9eDthcT7tYMFrMcMo+tSsPYUluIoOxyxokpi3LmvYigyoxzXzjp3IEBq1GUVZJUlXiYrHSiytO+tTiJIlmVkwipIydtp0xm8bmFKmNSzM6mNC/7aUqNANvaMdYeVx9ZCfvUD/Xq8gqAdunMrHPvls688zzTW9LZeT9PC1/is8qsydoxFsGm8hmJ48hn22mTWd/MC1Jf87Qwm2KEBDzSkM8Omr9MxS1NtIXYyjnrUjClz3E01VO9U6dSKm+Hqcxi5y6ZqlI710pXna3mcGuiq+McbiONMYudeZHqNpMJMtHGfJTJ/HqsVgX3B53l1acEpQm2wEQW2t0epXVmQbPx8vn211OVPVYmYNtYYOrqy95sp28uwvHOapaY2h4B2WRmtGYNo2aQCg69Xwvc3zJAyzolQeAMOFlLeZ5N8ntuLD9AOGua7B7i3OpS4q2R5m1YjTarvN2eIZSajVIrzvDWK7z2gyLj8sYwdXECrxjW1jS2sMyb+bj9fUQfHhiXPd+LwNTujGgDGHPN0FDG43ByBIZtoaQyh7pQV44SdiosRtS5FS5mtluzV+XTY+K5NZjw9GHreyHXyBJV6fMG2rx06KdTgf2oq6Zfj8F5Qf7sCcCYTzvFDuJu8YndoiBgORV+RRHB5rf6MmUssxHKB8/XgL8mpJCdhPFpggOpU5iVEbyB+eoldK6+S8x2PRNhXlOjAdxG/LDGCpYjwM0A9ngJwhJgMs33ANjMxB4ToNAdFHtqqy+bCFLfFO7CigAjzn7D9YxQONeJuNrHuhUbsyfODl4p2pjLRrp9vVTosvm2ms3z5rtXdejDVGc2LLzoSlqY1AvFmMqEe8P5dsZaR/q0GnK1gjTvJD88tuhx2P8+bOFY+rLV7dnC7GlrtrCZMVkxecVzuzpkLumAKSdYwPgpyaKwqMMkC7P29O3u7vpf6V+rt7Fvmr7ZQ+vCQuR3mL41Cv7M8mBNTl1bHvQq+yXWiP7gcPlvRQWiy7+Ecr78AQ==
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set - ex
4 |
5 | # Create go binary and package verifier + mock service into distribution
6 | VERSION=$(go version)
7 | echo "==> Go version ${VERSION}"
8 |
9 | echo "==> Getting dependencies..."
10 | export GO15VENDOREXPERIMENT=1
11 |
12 | go get github.com/mitchellh/gox
13 | go get github.com/inconshreveable/mousetrap # windows dep
14 | go get -d ./...
15 | gox -os="darwin" -arch="amd64" -output="cmd/MockXServer_{{.OS}}_{{.Arch}}"
16 | gox -os="windows" -arch="386" -output="cmd/MockXServer_{{.OS}}_{{.Arch}}"
17 | gox -os="linux" -arch="386" -output="cmd/MockXServer_{{.OS}}_{{.Arch}}"
18 | gox -os="linux" -arch="amd64" -output="cmd/MockXServer_{{.OS}}_{{.Arch}}"
19 |
20 | echo
21 | echo "==> Results:"
22 | ls -hl cmd/
23 | #echo "packaging ..."
24 | #tar -czf MockXServer.tar.gz MockXServer/
25 |
--------------------------------------------------------------------------------
/cmd/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC9zCCAd+gAwIBAgIQIiLjW/T/z/C82zPm6RDjlzANBgkqhkiG9w0BAQsFADAS
3 | MRAwDgYDVQQKEwdBY21lIENvMB4XDTE1MDkyOTExMTU1N1oXDTE2MDkyODExMTU1
4 | N1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
5 | AQoCggEBALEvsJ3DrrJrZmcSwrTD7nxvnbQRnikfENmt0t9Y3BrSJ/BGog62m1zc
6 | 5ZneJOv1C314D/H0wqdcpPRUenAkPUTa6Yj6RuUFIS7i8LgjsQ/ZR6HDSIDFd9ax
7 | AnZKWDIzBrcSadZvLoMq4epTjCFRXnQ+S2tqA7yqcrzIfyF3rSQPaU27NNZMwfRR
8 | Vgn1vHHHadf67KYqaTyQaZwhTI4cggHVYSEuTBejAUBabgFfHpG/G0mJ9e7+DOD0
9 | Au6hxXCh7SVoo3PKLQ4OHKdNhXITxm9u24tOszVf2Y/WaOsv1ZkJ9esaAeQ4Qmts
10 | zEIb+cJdP1U1x/ePE9q6idZZXwQShKMCAwEAAaNJMEcwDgYDVR0PAQH/BAQDAgWg
11 | MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwEgYDVR0RBAswCYIH
12 | amV0aG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAk0QwFO971DgGA9XSZlkW5lxqDVHZ
13 | C8R7RUotlamAVFW/Mx+kht/4ifaON3AvXTuizyR2rgMCvC8sT/2Z/zHWh+UYwrhf
14 | lPak9YK1UXxOI0XKZxIu82MX59vB9dvaRfPaSyaeEIOs+FtuQ0NRiUyXIrEY916X
15 | kMdhoLWtlwFsChzgSWYzWxzpZotdaSlFncu/wosX1F4jy/Y0n7Fc2zzMWatCS3cP
16 | DrHnNmWtCvBWPZ+kvuui/pWa6r0khPTK4NdbOuFMIvqq3cgNipH4yk7PAEZGVnwh
17 | 4HIHba/0Jj8mQJOn9blGTkfTwjRir/mcVKAorSj/yMsVxIQUVr5x1W1WrA==
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/cmd/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "RunMode":"online",
3 | "TLS":"off",
4 | "RemoteServer":"http://localhost:30008",
5 | "ListenOn":"0.0.0.0:9595",
6 | "OfflineHandler":"off",
7 | "LogFile":""
8 | }
9 |
--------------------------------------------------------------------------------
/cmd/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAsS+wncOusmtmZxLCtMPufG+dtBGeKR8Q2a3S31jcGtIn8Eai
3 | DrabXNzlmd4k6/ULfXgP8fTCp1yk9FR6cCQ9RNrpiPpG5QUhLuLwuCOxD9lHocNI
4 | gMV31rECdkpYMjMGtxJp1m8ugyrh6lOMIVFedD5La2oDvKpyvMh/IXetJA9pTbs0
5 | 1kzB9FFWCfW8ccdp1/rspippPJBpnCFMjhyCAdVhIS5MF6MBQFpuAV8ekb8bSYn1
6 | 7v4M4PQC7qHFcKHtJWijc8otDg4cp02FchPGb27bi06zNV/Zj9Zo6y/VmQn16xoB
7 | 5DhCa2zMQhv5wl0/VTXH948T2rqJ1llfBBKEowIDAQABAoIBADe1M8VWac2k4MxV
8 | ZKwq0geDnESqKVzqbITUFvGr4X/5RR9RRdB0b0JyHSoUYu1g3Nz066Z5+t1dzmsX
9 | SGuLqUvFvBkZ/0IwK6+vIFn0ts2x1cixOlFqRHRWsNx6IWKfEmRqwKXvzOLmj6L5
10 | 45vyFk1b7KB383bU7EAYlYfzNZeZTuOW6Ool2MkglQEtk3kJXt946/1k12lliErS
11 | WLam7iAWT/aLNyQrXrycRZvcBSOIRj/+C/wgSMm3WgYOuikXOJAsGZHMLpKy4LYz
12 | p9C+CaVAAWSq0AVQD3Ph/uyVpzkJkh6jHVMxyTGk3ZXBccXNviXZ+GiBHgfCivaQ
13 | RvhoZ8kCgYEA2MOM+6hydiJLaIx4zm51LJM+hQMDmN7CsTrahzWviv16eDzglTLV
14 | SNsnssU9+9VaMGd3ef+uC8C4ZTWqTe+J3Q39pmsPIrl5O5EIrsR/DJp8N0gE0sf7
15 | 7/wZau1xWutgkabzteN9l9d+WJ1Aj0MkNZXNlsF/JA/1q5j8Esqg2Y8CgYEA0UIv
16 | XDCQbEKea5k2J1Fr6KZBfJ4owSgB9I7qN3LxeLAWxmRFMgMy/akItcqdSlmHq62T
17 | jyDFEORldsSGNJPZiUv58bEuO45ynucHE+ycT+/jQ9ScjQwSHVY+1uwhwC7Zy1Wj
18 | 0ZiZz96v2x3+DEs4pB9+elvZnJBn07PjVeHDEa0CgYB7iriHlfHmspUX+TfBL+NI
19 | jINNe/JI5m1AZbK27TonlWB/tvKpFlfJNl7h3Nj+9xJ4FABcYATqXeS6imOOF8AT
20 | ZS3z84Ck4eDeukKgmQ1wOXwiZcGaALDujlhOQRYiI5TebrDoMcgbCo7QIo/xFutE
21 | PyT1j0LPYuk56+9/Jk2WsQKBgAv+KxCxh9Vsi//GrB/cvr56qM4nklZfoJ0Pg8Th
22 | xY0RKnz8SirvnACFq4YfspwMPYZNUGOnkSPCsz33TWm9/32q6Qw9B1VKeK86tmEh
23 | 8mNycCf3C3HZ+z/LLZbLubN1++13XwgoIsiTDqkUMvcpaHmfhHQmx3X9oLW1XZYS
24 | dkeRAoGBAKjMIbKOOkTUSBLDhGMvVCfm0nnfPYVigZuJyf265nCbPWW9QBOPXieK
25 | 2+pbleuOvi8QipOVd/b09Qwa05dYS/LJY87c/nwD1ijqa63sWJ/KmOQxQ/edVqRW
26 | wKVouqMTmhpWuf8V+Lo78UbTkKFHystFnmOxmbcdPqnZZTJoMc3F
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/db/initFromJsons.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | "strconv"
7 | "strings"
8 | "time"
9 |
10 | "github.com/compasses/MockXServer/utils"
11 | )
12 |
13 | func (replay *ReplayDB) ReadJsonFiles(filePath string) {
14 | now := time.Now()
15 | log.Println("going to read file", filePath)
16 | stream, err := ioutil.ReadFile(filePath)
17 | if err != nil {
18 | log.Println("going to read file error happened ", err, stream)
19 | return
20 | }
21 |
22 | res, err := utils.TOJsonInterface(stream)
23 | paths := res.(map[string]interface{})["paths"]
24 | pathsMap := paths.(map[string]interface{})
25 | for path, val := range pathsMap {
26 | valMap := val.(map[string]interface{})
27 | for method, detail := range valMap {
28 | detailMap := detail.([]interface{})
29 | for _, detailMapel := range detailMap {
30 | detailMapItem := detailMapel.(map[string]interface{})
31 | request, ok := detailMapItem["request"]
32 | if !ok {
33 | log.Println("missing request, continue ", detailMapItem)
34 | continue
35 | }
36 | respose, ok := detailMapItem["response"]
37 | if !ok {
38 | log.Println("missing response, continue ", detailMapItem)
39 | continue
40 | }
41 | responseMap := respose.(map[string]interface{})
42 | for k, v := range responseMap {
43 | status, _ := strconv.Atoi(k)
44 | //fmt.Println("\r\nstore:", request, "response", v)
45 | replay.StoreRequestFromJson(path, method, request, v, status)
46 | break
47 | }
48 | }
49 | }
50 | }
51 | log.Println("Time Used:", time.Since(now))
52 | }
53 |
54 | func (replay *ReplayDB) ReadDir(dir string) {
55 | files, err := ioutil.ReadDir(dir)
56 | if err != nil {
57 | log.Println(err)
58 | return
59 | }
60 |
61 | for _, file := range files {
62 | if strings.Contains(file.Name(), "Incomplete") {
63 | continue
64 | }
65 | replay.ReadJsonFiles(dir + "/" + file.Name())
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/db/replayDB.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 |
9 | "github.com/boltdb/bolt"
10 | "github.com/compasses/MockXServer/utils"
11 | )
12 |
13 | type ReplayDB struct {
14 | db *bolt.DB
15 | reqRspKey string
16 | }
17 |
18 | func NewReplayDB(path string) (*ReplayDB, error) {
19 | dbopen, err := bolt.Open(path, 0600, nil)
20 | if err != nil {
21 | return nil, err
22 | }
23 |
24 | return &ReplayDB{db: dbopen, reqRspKey: "|"}, nil
25 | }
26 |
27 | func (replay *ReplayDB) GetDBFilePath() string {
28 | return replay.db.Path()
29 | }
30 |
31 | func (replay *ReplayDB) StoreRequestFromJson(path, method string, reqBody, respBody interface{}, statusCode int) {
32 | req := utils.JsonInterfaceToByte(reqBody)
33 | rsp := utils.JsonInterfaceToByte(respBody)
34 |
35 | err := replay.StoreRequest(path, method, string(req), string(rsp), statusCode)
36 | if err != nil {
37 | log.Println("Store for json failed ", err)
38 | }
39 | }
40 |
41 | func (replay *ReplayDB) StoreRequest(path, method, reqBody, respBody string, statusCode int) (err error) {
42 | if len(reqBody) == 0 {
43 | reqBody = replay.reqRspKey
44 | }
45 |
46 | finalReq, finalResp, err := utils.JsonNormalize(reqBody, respBody, statusCode)
47 | if err != nil {
48 | log.Println("JSON Normalize error ", err)
49 | }
50 | log.Println("going save req: ", path, " rqbody ", reqBody)
51 |
52 | replay.db.Update(func(tx *bolt.Tx) error {
53 | pathBucket, err := tx.CreateBucketIfNotExists([]byte(path))
54 | if err != nil {
55 | err = fmt.Errorf("create bucket: %s", err)
56 | return err
57 | }
58 |
59 | err = pathBucket.Put([]byte(method+replay.reqRspKey), finalResp)
60 | if err != nil {
61 | err = fmt.Errorf("store bucket error: %s", err)
62 | return err
63 | }
64 |
65 | methodBucket, err := pathBucket.CreateBucketIfNotExists([]byte(method))
66 | err = methodBucket.Put(finalReq, finalResp)
67 | if err != nil {
68 | err = fmt.Errorf("store bucket error: %s", err)
69 | return err
70 | }
71 | return nil
72 | })
73 | return
74 | }
75 |
76 | func (replay *ReplayDB) GetResponse(path, method, reqBody string) (resp []byte, err error) {
77 | if len(reqBody) == 0 {
78 | reqBody = replay.reqRspKey
79 | }
80 |
81 | replay.db.View(func(tx *bolt.Tx) error {
82 |
83 | pathBucket := tx.Bucket([]byte(path))
84 | if pathBucket == nil {
85 | err = fmt.Errorf("No response found for path:%s", path)
86 | return err
87 | }
88 | methodBucket := pathBucket.Bucket([]byte(method))
89 | if methodBucket == nil {
90 | err = fmt.Errorf("No response for path and method:%s", path+method)
91 | return err
92 | }
93 |
94 | req := []byte(reqBody)
95 | // req, err = utils.JsonNormalizeSingle(reqBody)
96 | // if err != nil {
97 | // return err
98 | // }
99 | resp = methodBucket.Get(req)
100 | if resp == nil {
101 | //use fuzzy match to get result
102 | resp = pathBucket.Get([]byte(method + replay.reqRspKey))
103 | if resp == nil {
104 | err = fmt.Errorf("No response for path and method:%s", path+method)
105 | }
106 | }
107 | return nil
108 | })
109 | return
110 | }
111 |
112 | func (replay *ReplayDB) Close() {
113 | err := replay.db.Close()
114 | if err != nil {
115 | fmt.Println("Close error:", err)
116 | }
117 | }
118 |
119 | func (replay *ReplayDB) Open(path string) {
120 | db, err := bolt.Open(path, 0600, nil)
121 | replay.db = db
122 | if err != nil {
123 | log.Println("Open DB error:", err)
124 | }
125 | }
126 |
127 | func (replay *ReplayDB) GetDB() *bolt.DB {
128 | return replay.db
129 | }
130 |
131 | func (replay *ReplayDB) GetJSONMap() (outmap map[string]map[string][]interface{}) {
132 | outmap = make(map[string]map[string][]interface{})
133 |
134 | replay.db.View(func(tx *bolt.Tx) error {
135 | cur := tx.Cursor()
136 | buc := cur.Bucket()
137 | c := buc.Cursor()
138 |
139 | for k, v := c.First(); k != nil; k, v = c.Next() {
140 | //fmt.Printf("Out side key=%s, value=%s\n", k, v)
141 | if v == nil {
142 | bb := tx.Bucket(k)
143 | if bb == nil {
144 | continue
145 | }
146 | cc := bb.Cursor()
147 | for kk, vv := cc.First(); kk != nil; kk, vv = cc.Next() {
148 | //fmt.Printf("middel layer key=%s, value=%s\n", kk, vv)
149 | if vv == nil {
150 | bbb := bb.Bucket(kk)
151 | if bbb == nil {
152 | continue
153 | }
154 | ccc := bbb.Cursor()
155 | for kkk, vvv := ccc.First(); kkk != nil; kkk, vvv = ccc.Next() {
156 | //fmt.Printf("last layer key=%s, value=%s\n", kkk, vvv)
157 | if outmap[string(k)] == nil {
158 | outmap[string(k)] = make(map[string][]interface{})
159 | }
160 | var pamv interface{}
161 | var resp interface{}
162 |
163 | err := json.Unmarshal(kkk, &pamv) //string(kkk)
164 | if err != nil {
165 | log.Println("Unmarshal failed ", err)
166 | }
167 | err = json.Unmarshal(vvv, &resp)
168 | if err != nil {
169 | log.Println("Unmarshal failed ", err)
170 | }
171 | outmap[string(k)][string(kk)] = append(outmap[string(k)][string(kk)], map[string]interface{}{
172 | "request": pamv,
173 | "response": resp,
174 | })
175 | }
176 | }
177 | }
178 | }
179 | }
180 | return nil
181 | })
182 | return
183 | }
184 |
185 | func (replay *ReplayDB) SerilizeToFile() (filename string) {
186 | filename = "./single.json"
187 | outmap := replay.GetJSONMap()
188 |
189 | result := map[string]interface{}{
190 | "paths": outmap,
191 | }
192 |
193 | jsonStr, err := json.MarshalIndent(result, "", " ")
194 |
195 | err = ioutil.WriteFile(filename, jsonStr, 0666)
196 | if err != nil {
197 | log.Println(err)
198 | }
199 | return
200 | }
201 |
--------------------------------------------------------------------------------
/getversion.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | version=`git log --date=iso --pretty=format:"%cd @%h" -1`
3 | if [ $? -ne 0 ]; then
4 | version="not a git repo"
5 | fi
6 |
7 | compile=`date +"%F %T %z"`" by "`go version`
8 |
9 | cat << EOF | gofmt > version.go
10 | package main
11 |
12 | const (
13 | Version = "$version"
14 | Compile = "$compile"
15 | )
16 | EOF
17 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/compasses/MockXServer/middleware"
7 | )
8 |
9 | const banner string = `
10 |
11 | Mock Server
12 |
13 | `
14 |
15 | func main() {
16 | log.Println(banner)
17 | log.Printf("Git commit:%s\n", Version)
18 | log.Printf("Build time:%s\n", Compile)
19 | middle := middleware.NewMiddleware()
20 | middle.Run()
21 | }
22 |
--------------------------------------------------------------------------------
/middleware/middlehandler.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "log"
9 | "net/http"
10 | "os"
11 | "strconv"
12 | "strings"
13 | )
14 |
15 | func (middleware *middleWare) SaveNotFound(path, method, reqBody, respBody string, statusCode int) (err error) {
16 | if err != nil {
17 | log.Println("JSON Normalize error ", err)
18 | }
19 |
20 | filename := strings.Replace(path+"Incomplete.json", "/", "_", -1)
21 | filename = "./input/" + filename
22 |
23 | result := map[string]interface{}{
24 | path: map[string]interface{}{
25 | method: map[string]interface{}{
26 | "request": reqBody,
27 | "response": respBody,
28 | },
29 | },
30 | }
31 |
32 | jsonStr, err := json.MarshalIndent(result, "", " ")
33 |
34 | err = ioutil.WriteFile(filename, jsonStr, 0666)
35 | if err != nil {
36 | log.Println(err)
37 | }
38 | return nil
39 | }
40 |
41 | func (middleware *middleWare) returnFile(w http.ResponseWriter, filename, attachName string) {
42 | f, err := os.Open(filename)
43 | if err != nil {
44 | w.WriteHeader(500)
45 | w.Write([]byte(err.Error()))
46 | return
47 | }
48 |
49 | fileInfo, err := f.Stat()
50 | if err != nil {
51 | w.WriteHeader(500)
52 | w.Write([]byte(err.Error()))
53 | return
54 | }
55 | w.Header().Set("Content-Type", "application/octet-stream")
56 | w.Header().Set("Content-Disposition", `attachment; filename=`+attachName)
57 | w.Header().Set("Content-Length", strconv.Itoa(int(fileInfo.Size())))
58 | io.Copy(w, f)
59 | }
60 |
61 | func (middleware *middleWare) TestTruncate() {
62 | dbFile := middleware.replaydb.GetDBFilePath()
63 | middleware.replaydb.Close()
64 | fmt.Println("DB file ", dbFile)
65 | db, err := os.OpenFile(dbFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
66 | if err != nil {
67 | fmt.Println("OPen error ", err)
68 | }
69 | err = db.Truncate(0)
70 | if err != nil {
71 | fmt.Println("Truncate error ", err)
72 | }
73 | }
74 |
75 | func (middleware *middleWare) Truncate(w http.ResponseWriter) {
76 | dbFile := middleware.replaydb.GetDBFilePath()
77 | middleware.replaydb.Close()
78 | db, err := os.OpenFile(dbFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
79 | if err != nil {
80 | w.WriteHeader(500)
81 | w.Write([]byte(err.Error()))
82 | log.Println("OPen error ", err)
83 | }
84 | err = db.Truncate(0)
85 | if err != nil {
86 | w.WriteHeader(500)
87 | w.Write([]byte(err.Error()))
88 | log.Println("Truncate error ", err)
89 | }
90 | w.WriteHeader(200)
91 | w.Write([]byte("
Truncate successful
"))
92 | }
93 |
94 | func (middleware *middleWare) TestPactGen() {
95 | middleware.replaydb.ReadDir("./input")
96 | middleware.GenPactWithProvider()
97 | }
98 |
99 | func (middleware *middleWare) GenerateJSON(w http.ResponseWriter) {
100 | middleware.replaydb.ReadDir("./input")
101 | filename := middleware.replaydb.SerilizeToFile()
102 | middleware.returnFile(w, filename, "JSONFile.json")
103 | }
104 |
105 | func (middleware *middleWare) GeneratePACT(w http.ResponseWriter) {
106 | middleware.replaydb.ReadDir("./input")
107 | middleware.GenPactWithProvider()
108 | pactfile := middleware.GetPactFile()
109 | if len(pactfile) > 0 {
110 | middleware.returnFile(w, pactfile, "ConsumerContracts.json")
111 | } else {
112 | log.Println("No Pact File Generate ")
113 | w.Write([]byte("No Pact File Generate "))
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/middleware/middleware.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "io/ioutil"
7 | "log"
8 | "net/http"
9 | "os"
10 | "strconv"
11 | "strings"
12 | "time"
13 |
14 | "github.com/compasses/MockXServer/db"
15 | "github.com/compasses/MockXServer/offline"
16 | "github.com/compasses/MockXServer/online"
17 | "github.com/compasses/MockXServer/utils"
18 | )
19 |
20 | type config struct {
21 | RunMode string
22 | TLS string
23 | RemoteServer string
24 | ListenOn string
25 | OfflineHandler string
26 | LogFile string
27 | }
28 |
29 | func GetConfiguration() (conf *config, err error) {
30 | //get configuration
31 | file, err := os.Open("./config.json")
32 | if err != nil {
33 | log.Println("read file failed...", err)
34 | log.Println("Just run in offline mode")
35 | return nil, err
36 | }
37 |
38 | data, err := ioutil.ReadAll(file)
39 | if err != nil {
40 | log.Println("read file failed...", err)
41 | log.Println("Just run in offline mode")
42 | return nil, err
43 | } else {
44 | json.Unmarshal(data, &conf)
45 | log.Println("get configuration:", string(data))
46 | }
47 |
48 | return conf, nil
49 | }
50 |
51 | type middleWare struct {
52 | conf *config
53 | handler http.Handler
54 | replaydb *db.ReplayDB
55 | runmode int
56 | }
57 |
58 | func NewMiddleware() *middleWare {
59 | conf, err := GetConfiguration()
60 | if err != nil {
61 | return nil
62 | }
63 | // set log format
64 | if len(conf.LogFile) > 0 {
65 | f, err := os.OpenFile(conf.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
66 | if err != nil {
67 | log.Println("error opening file: %v", err)
68 | }
69 |
70 | log.SetOutput(f)
71 | go func(f *os.File) {
72 | for {
73 | f.Sync()
74 | time.Sleep(time.Second)
75 | }
76 | }(f)
77 | } else {
78 | log.Println("Not assign log file, just print to command window.")
79 | }
80 | log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds)
81 |
82 | middleware := new(middleWare)
83 | middleware.conf = conf
84 | dbreplay, err := db.NewReplayDB("./ReplayDB")
85 | dbreplay.ReadDir("./input")
86 | if err != nil {
87 | log.Println("Open replayDB error ", err)
88 | return nil
89 | }
90 | middleware.replaydb = dbreplay
91 |
92 | if conf.RunMode == "offline" {
93 | log.Println("MockXServer Run in offline mode...")
94 | offlineDB, err := db.NewReplayDB("./OfflineDB")
95 | if err != nil {
96 | log.Println("Open OfflineDB error ", err)
97 | return nil
98 | }
99 |
100 | middleware.handler = offline.NewServerRouter(offlineDB.GetDB())
101 | middleware.runmode = 0
102 | } else {
103 | log.Println("MockXServer Run in online mode...")
104 | middleware.handler = online.NewProxyHandler(conf.RemoteServer, middleware.replaydb)
105 | middleware.runmode = 1
106 | }
107 |
108 | return middleware
109 | }
110 |
111 | func (middleware *middleWare) PreHandle(w http.ResponseWriter, req *http.Request) bool {
112 | path := strings.Split(req.RequestURI, "?")
113 | log.Println("prehandle path: ", req.RequestURI)
114 |
115 | if path[0] == "/json" {
116 | middleware.GenerateJSON(w)
117 | return true
118 | } else if path[0] == "/pact" {
119 | middleware.GeneratePACT(w)
120 | return true
121 | } else if path[0] == "/truncate" {
122 | middleware.Truncate(w)
123 | return true
124 | }
125 |
126 | return false
127 | }
128 |
129 | func (middle *middleWare) Run() {
130 | log.Println("Listen ON: ", middle.conf.ListenOn)
131 | if middle.conf.TLS == "on" {
132 | log.Fatal(http.ListenAndServeTLS(middle.conf.ListenOn, "cert.pem", "key.pem", middle))
133 | } else {
134 | log.Fatal(http.ListenAndServe(middle.conf.ListenOn, middle))
135 | }
136 | }
137 |
138 | func (middleware *middleWare) HandleOffline(w http.ResponseWriter, req *http.Request) {
139 | newbody := make([]byte, req.ContentLength)
140 | req.Body.Read(newbody)
141 | log.Println("offline handle , try to get ", req.RequestURI, req.Method, string(newbody))
142 |
143 | res, err := middleware.replaydb.GetResponse(req.RequestURI, req.Method, string(newbody))
144 | if err != nil || res == nil {
145 | log.Println("Cannot get response from replaydb on offline mode", err)
146 | if middleware.conf.OfflineHandler != "off" {
147 | log.Println("Need handle request in offline handler")
148 | middleware.SaveNotFound(req.RequestURI, req.Method, string(newbody), "...xxx...", 200)
149 |
150 | newRq, err := http.NewRequest(req.Method, req.RequestURI, ioutil.NopCloser(bytes.NewReader(newbody)))
151 | if err != nil {
152 | log.Println("new http request failed ", err)
153 | }
154 | utils.RequstFormat(true, newRq, string(newbody))
155 | middleware.handler.ServeHTTP(w, newRq)
156 | }
157 | } else {
158 | result, _ := utils.TOJsonInterface(res)
159 | log.Println("Get response from replaydb on offline mode ", (result))
160 | resultmap := result.(map[string]interface{})
161 | for key, value := range resultmap {
162 | status, _ := strconv.Atoi(key)
163 | w.WriteHeader(status)
164 | stream := []byte(value.(string))
165 | _, err = w.Write(stream)
166 | if err != nil {
167 | log.Println("Get response from replaydb but write error ", err)
168 | }
169 | break
170 | }
171 | }
172 | }
173 |
174 | func (middleware *middleWare) ServeHTTP(w http.ResponseWriter, req *http.Request) {
175 | w.Header().Set("Content-Type", "application/json")
176 | preHandled := middleware.PreHandle(w, req)
177 | if preHandled {
178 | return
179 | }
180 |
181 | if middleware.conf.RunMode == "online" {
182 | middleware.handler.ServeHTTP(w, req)
183 | } else {
184 | // firstly try to get from replaydb
185 | middleware.HandleOffline(w, req)
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/middleware/pactgenerate.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | "net/http"
9 | "strconv"
10 |
11 | "github.com/SEEK-Jobs/pact-go"
12 | "github.com/SEEK-Jobs/pact-go/provider"
13 | "github.com/compasses/MockXServer/utils"
14 | )
15 |
16 | const PactsDir = "./pacts"
17 |
18 | type ProviderAPIClient struct {
19 | baseURL string
20 | }
21 |
22 | func (c *ProviderAPIClient) ClientRun(method, path string, reqBody interface{}) error {
23 | url := fmt.Sprintf("%s%s", c.baseURL, path)
24 | reqb := utils.JsonInterfaceToByte(reqBody)
25 |
26 | log.Println("going to verify: ", url+" "+method)
27 |
28 | req, err := http.NewRequest(method, url, bytes.NewBuffer(reqb))
29 | if err != nil {
30 | log.Println(err)
31 | return err
32 | }
33 |
34 | client := &http.Client{}
35 | resp, err := client.Do(req)
36 |
37 | if err != nil {
38 | log.Println(err)
39 | return err
40 | }
41 | defer resp.Body.Close()
42 |
43 | res, err := ioutil.ReadAll(resp.Body)
44 | if err != nil {
45 | log.Println("ioutil read err ", err, " body", string(res))
46 | return err
47 | }
48 | //log.Println("Got response: ", string(res))
49 |
50 | return nil
51 | }
52 |
53 | func (middleware *middleWare) GetPactFile() string {
54 | files, err := ioutil.ReadDir(PactsDir)
55 | if err != nil {
56 | log.Println(err)
57 | return ""
58 | }
59 | //now just run the first file
60 | for _, file := range files {
61 | log.Println("upload pact file name ", file.Name())
62 | return PactsDir + "/" + file.Name()
63 | }
64 | return ""
65 | }
66 |
67 | func (middleware *middleWare) buildPact(consumerName, providerName string) pact.Builder {
68 | return pact.
69 | NewConsumerPactBuilder(&pact.BuilderConfig{PactPath: PactsDir}).
70 | ServiceConsumer(consumerName).
71 | HasPactWith(providerName)
72 | }
73 |
74 | func (middleware *middleWare) RunPact(builder pact.Builder, path, method string, reqBody, respBody interface{}, statusCode int,
75 | consumerName, providerName string) {
76 | ms, msUrl := builder.GetMockProviderService()
77 |
78 | request := provider.NewJSONRequest(method, path, "", nil)
79 | if reqBody != nil {
80 | err := request.SetBody(reqBody)
81 | if err != nil {
82 | log.Println("Set request error ", err, " reqBody ", respBody)
83 | }
84 | }
85 |
86 | header := make(http.Header)
87 | header.Add("content-type", "application/json")
88 | response := provider.NewJSONResponse(statusCode, header)
89 | if respBody != nil {
90 | err := response.SetBody(respBody)
91 | if err != nil {
92 | log.Println("Set Response error ", err, " respBody ", respBody)
93 | }
94 | }
95 |
96 | //Register interaction for this test scope
97 | if err := ms.Given(consumerName).
98 | UponReceiving(providerName).
99 | With(*request).
100 | WillRespondWith(*response); err != nil {
101 | log.Println(err)
102 | }
103 |
104 | //log.Println("Register: ", " Request ", string(req), " response ", respBody)
105 |
106 | //test
107 | client := &ProviderAPIClient{baseURL: msUrl}
108 | if err := client.ClientRun(method, path, reqBody); err != nil {
109 | log.Println(err)
110 | }
111 |
112 | //Verify registered interaction
113 | if err := ms.VerifyInteractions(); err != nil {
114 | log.Println(err)
115 | }
116 |
117 | //Clear interaction for this test scope, if you need to register and verify another interaction for another test scope
118 | ms.ClearInteractions()
119 | }
120 |
121 | func (middleware *middleWare) GenPactWithProvider() {
122 | builder := middleware.buildPact("EShop Online Store", "EShop Adaptor")
123 | //map[string]map[string][]interface{}
124 | //"Path", "Method", "[req..., rsp...,]"
125 | interactMap := middleware.replaydb.GetJSONMap()
126 | for path, value := range interactMap {
127 | for method, interacts := range value {
128 | var count int = 0
129 | for _, detailMapel := range interacts {
130 | detailMapItem := detailMapel.(map[string]interface{})
131 | request, ok := detailMapItem["request"]
132 | if !ok {
133 | log.Println("missing request, continue ", detailMapItem)
134 | continue
135 | }
136 | respose, ok := detailMapItem["response"]
137 | if !ok {
138 | log.Println("missing response, continue ", detailMapItem)
139 | continue
140 | }
141 | responseMap := respose.(map[string]interface{})
142 | count++
143 | for k, v := range responseMap {
144 | status, _ := strconv.Atoi(k)
145 | //fmt.Println("\r\nstore:", request, "response", v)
146 | consumName := "mock server for " + path + " method " + method + " " + strconv.Itoa(count)
147 | provideName := "pact contract for " + path + " method " + method + " " + strconv.Itoa(count)
148 | middleware.RunPact(builder, path, method, request, v, status, consumName,
149 | provideName)
150 | count++
151 | break
152 | }
153 | }
154 | }
155 | }
156 |
157 | //Finally, build to produce the pact json file
158 | if err := builder.Build(); err != nil {
159 | log.Println(err)
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/middleware/test.json:
--------------------------------------------------------------------------------
1 | http://localhost:8080/sbo/api/v1/ChannelAccounts?filter=(account eq 'sss@ss2.com') and (salesChannel.id eq 2)
2 |
3 | [
4 | {
5 | "id": 1,
6 | "customerId": 2,
7 | "salesChannel": {
8 | "id": 3,
9 | "name": "YZ"
10 | },
11 | "contactId": null,
12 | "lastPurchase": "2015-09-21T22:10:00.000Z",
13 | "account": "sss@ss2.com",
14 | "resetPasswordLink": null,
15 | "unifiedAccount": "sss@ss2.com"
16 | }
17 | ]
18 |
--------------------------------------------------------------------------------
/offline/logger.go:
--------------------------------------------------------------------------------
1 | package offline
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "time"
7 |
8 | "github.com/julienschmidt/httprouter"
9 | )
10 |
11 | func Logger(inner httprouter.Handle, name string) httprouter.Handle {
12 | return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
13 | log.Printf(
14 | "Start to handle : %s\t%s\t%s\t",
15 | r.Method,
16 | r.RequestURI,
17 | name,
18 | )
19 | start := time.Now()
20 | //time.Sleep(time.Millisecond * 1000)
21 |
22 | inner(w, r, ps)
23 |
24 | log.Printf(
25 | "%s\t%s\t%s\t%s",
26 | r.Method,
27 | r.RequestURI,
28 | name,
29 | time.Since(start),
30 | )
31 | })
32 | }
33 |
34 | func LoggerNotFound(inner http.HandlerFunc) http.HandlerFunc {
35 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36 | start := time.Now()
37 |
38 | inner.ServeHTTP(w, r)
39 |
40 | log.Printf(
41 | "%s\t%s\t%s\t%s",
42 | r.Method,
43 | r.RequestURI,
44 | "404 Not Found",
45 | time.Since(start),
46 | )
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/offline/modals.go:
--------------------------------------------------------------------------------
1 | package offline
2 |
3 | import . "github.com/compasses/MockXServer/utils"
4 |
5 | type ATSReq struct {
6 | SkuIds IDSeqs
7 | ChanelId TableId
8 | }
9 |
10 | type ATSRsp struct {
11 | SkuId TableId `json:"skuId"`
12 | Ats int64 `json:"ats"`
13 | AllowBackOrder bool `json:"allowBackOrder"`
14 | }
15 |
16 | type RecommandInfo struct {
17 | ChannelId int
18 | ProductId TableId `json:",string"`
19 | CurrencyId int
20 | }
21 |
22 | type Customer struct {
23 | Id TableId `json:"id,string"`
24 | CustomerType string `json:"customerType"`
25 | Email string `json:"email"`
26 | }
27 |
28 | type CustomerCreateRsp struct {
29 | CustomerCode string `json:"customerCode"`
30 | CustomerID TableId `json:"customerID"`
31 | ChannelAccountID TableId `json:"channelAccountID"`
32 | //FailType string `json:"failType"`
33 | }
34 |
35 | type CustomerAddress struct {
36 | Id TableId `json:"id"`
37 | CustomerInfo Customer `json:"customer"`
38 | AddressInfo interface{} `json:"address"`
39 | DefaultBillTo bool `json:"defaultBillTo"`
40 | DefaultShipTo bool `json:"defaultShipTo"`
41 | }
42 |
43 | type CustomerCreate struct {
44 | ChannelId TableId
45 | Account string
46 | Customer Customer
47 | CustomerType string
48 | AccountInfo CustomerCreateRsp
49 | Addresses []CustomerAddress
50 | }
51 |
52 | type CartItems struct {
53 | SkuId interface{} `json:"skuId"`
54 | UnitPrice interface{} `json:"unitPrice"`
55 | Quantity interface{} `json:"quantity"`
56 | TaxAmount interface{} `json:"taxAmount"`
57 | DiscountPercentage interface{} `json:"discountPercentage"`
58 | LineTotal interface{} `json:"lineTotal"`
59 | LineTotalAfterDisc interface{} `json:"lineTotalAfterDisc"`
60 | StandardPrice interface{} `json:"standardPrice"`
61 | Remark interface{} `json:"remark"`
62 | }
63 |
64 | type ShoppingCart struct {
65 | CartTotal interface{} `json:"cartTotal"`
66 | DiscountPercentage interface{} `json:"discountPercentage"`
67 | DiscountSum interface{} `json:"discountSum"`
68 | PriceMethod interface{} `json:"priceMethod"`
69 | CartItems []CartItems `json:"cartItems"`
70 | }
71 |
72 | type CheckoutCartPlayLoad struct {
73 | ShippingAddress interface{} `json:"shippingAddress"`
74 | BillingAddress interface{} `json:"billingAddress"`
75 | CustomerId interface{} `json:"customerId"`
76 | ChannelAccountId interface{} `json:"channelAccountId"`
77 | ChannelId interface{} `json:"channelId"`
78 | ShoppingCart ShoppingCart `json:"shoppingCart"`
79 | ShippingMethod interface{} `json:"shippingMethod"`
80 | Promotion interface{} `json:"promotion"`
81 | TaxTotal interface{} `json:"taxTotal"`
82 | OrderTotal interface{} `json:"orderTotal"`
83 | DiscountPercentage interface{} `json:"discountPercentage"`
84 | DiscountSum interface{} `json:"discountSum"`
85 | }
86 |
87 | type CheckoutShoppingCart struct {
88 | ShoppingCart CheckoutCartPlayLoad `json:"shoppingCart"`
89 | }
90 |
91 | type CheckoutShoppingCartRsp struct {
92 | CheckoutCartPlayLoad
93 | ShippingCosts interface{} `json:"shippingCosts"`
94 | EnableExpressDelivery bool `json:"enableExpressDelivery"`
95 | }
96 |
97 | type OrderCreate struct {
98 | EShopOrder CheckoutCartPlayLoad `json:"eShopOrder"`
99 | }
100 |
--------------------------------------------------------------------------------
/offline/repo.go:
--------------------------------------------------------------------------------
1 | package offline
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "strconv"
7 |
8 | "github.com/boltdb/bolt"
9 | . "github.com/compasses/MockXServer/utils"
10 | )
11 |
12 | var GlobalDB *bolt.DB
13 |
14 | //tables define
15 | const (
16 | ProductTable = "PRODUCTS"
17 | SKUTable = "PRODUCT-SKU"
18 | DefaultATS TableId = 10
19 | CustomerTable = "CUSTOMER"
20 | AddressTable = "ADDRESS"
21 | OrderTable = "ORDERS"
22 | RecommandProductsNum = 15
23 | )
24 |
25 | func RepoCheckoutShoppingCart(checkoutCart CheckoutCartPlayLoad) CheckoutShoppingCartRsp {
26 | result := CheckoutShoppingCartRsp{checkoutCart, nil, true}
27 |
28 | var cartTotal float64
29 | for i, val := range result.ShoppingCart.CartItems {
30 | var quantity float64
31 | switch v := val.Quantity.(type) {
32 | case string:
33 | quantity = ToFloat64FromString(v)
34 | default:
35 | quantity = float64(v.(float64))
36 | }
37 |
38 | price := ToFloat64FromString(val.UnitPrice.(string))
39 | result.ShoppingCart.CartItems[i].LineTotal = (float64)(quantity) * price
40 | cartTotal += result.ShoppingCart.CartItems[i].LineTotal.(float64)
41 | }
42 |
43 | result.ShoppingCart.CartTotal = cartTotal
44 | result.OrderTotal = cartTotal
45 | result.TaxTotal = cartTotal
46 |
47 | result.ShippingCosts = []map[string]interface{}{
48 | {
49 | "default": true,
50 | "identifier": "FIX-100-100",
51 | "type": "FIX",
52 | "id": -100,
53 | "carrierId": -100,
54 | "name": "EShopDefaultRate",
55 | "cost": "0.0",
56 | "minDeliveryDays": 2,
57 | "maxDeliveryDays": 5,
58 | },
59 | }
60 | return result
61 | }
62 |
63 | func RepoGetSalesOrders(channelAccountId string) (result interface{}) {
64 |
65 | GlobalDB.Update(func(tx *bolt.Tx) error {
66 | orderBucket, err := tx.CreateBucketIfNotExists([]byte(OrderTable))
67 | if err != nil {
68 | HandleError(err)
69 | return err
70 | }
71 |
72 | cusBuk := orderBucket.Bucket([]byte(channelAccountId))
73 | if cusBuk == nil {
74 | log.Println("not found this user " + channelAccountId)
75 | return nil
76 | }
77 | c := cusBuk.Cursor()
78 | var number int
79 | var orders []interface{}
80 |
81 | for k, v := c.First(); k != nil; k, v = c.Next() {
82 | var temp interface{}
83 | json.Unmarshal(v, &temp)
84 | orders = append(orders, temp)
85 | number++
86 | }
87 | //reverse the orders
88 | for i := len(orders)/2 - 1; i >= 0; i-- {
89 | opp := len(orders) - 1 - i
90 | orders[i], orders[opp] = orders[opp], orders[i]
91 | }
92 |
93 | result = map[string]interface{}{
94 | "value": orders,
95 | "odata.count": number,
96 | }
97 | return nil
98 | })
99 | return
100 | }
101 |
102 | func RepoGetSalesOrder(orderId TableId, channelAccountId string) (result interface{}) {
103 |
104 | GlobalDB.Update(func(tx *bolt.Tx) error {
105 | orderBucket, err := tx.CreateBucketIfNotExists([]byte(OrderTable))
106 | if err != nil {
107 | HandleError(err)
108 | return err
109 | }
110 |
111 | cusBuk := orderBucket.Bucket([]byte(channelAccountId))
112 | if cusBuk == nil {
113 | result = "not found this user " + channelAccountId
114 | return nil
115 | }
116 |
117 | orderBytes := cusBuk.Get(orderId.ToBytes())
118 |
119 | json.Unmarshal(orderBytes, &result)
120 |
121 | return nil
122 | })
123 | return
124 | }
125 |
126 | func GenerateOrder(order CheckoutCartPlayLoad, orderId uint64) interface{} {
127 | return map[string]interface{}{
128 | "id": orderId,
129 | "docNumber": orderId,
130 | "billingAddr": order.BillingAddress,
131 | "shippingAddr": order.ShippingAddress,
132 | "shippingCost": "0",
133 | "subTotal": "500",
134 | "grossDocTotal": "500",
135 | "taxTotal": "0",
136 | "customer": map[string]interface{}{
137 | "id": order.CustomerId,
138 | },
139 | "process": map[string]interface{}{
140 | "id": 3,
141 | "processName": "快递",
142 | },
143 | "salesOrderLines": []map[string]interface{}{
144 | {
145 | "amazonOrderItemCode": nil,
146 | "baseDocId": nil,
147 | "baseDocLineId": nil,
148 | "baseDocLineNumber": nil,
149 | "baseDocNumber": nil,
150 | "baseDocType": nil,
151 | "canelReason": nil,
152 | "costTotal": "0",
153 | "discountPercentage": "0",
154 | "docCurrencyId": 1,
155 | "exceptionFlag": false,
156 | "exceptionReasonDesc": nil,
157 | "giftMessage": nil,
158 | "grossLineTotal": "500.00",
159 | "grossLineTotalAfterDisc": "500.00",
160 | "grossLineTotalAfterDiscLC": "500.00",
161 | "grossLineTotalLC": "500.00",
162 | "grossProfitAmount": "0",
163 | "grossProfitRate": "0",
164 | "grossUnitPrice": "500.00",
165 | "id": 119,
166 | "inventoryUomName": "Unit",
167 | "inventoryUomQuantity": "1.00",
168 | "invoiceStatus": "tNotInvoiced",
169 | "isNonLogistical": false,
170 | "isPreparingStock": false,
171 | "isPromotionAppliable": true,
172 | "isService": false,
173 | "lineAction": "C,A,I,S",
174 | "lineCalcBase": "byTotal",
175 | "lineComments": nil,
176 | "lineNumber": 1,
177 | "lineType": "tProductLine",
178 | "logisticsStatus": "tAllocated",
179 | "merchantFulfillmentItemID": nil,
180 | "netLineTotal": "500.00",
181 | "netLineTotalAfterDisc": "500.00",
182 | "netLineTotalAfterDiscLC": "500.00",
183 | "netLineTotalLC": "500.00",
184 | "netUnitPrice": "500.00",
185 | "originLine": nil,
186 | "originSkuPrice": "500",
187 | "planningWhsId": nil,
188 | "priceSource": nil,
189 | "promotionDescription": nil,
190 | "promotionHintBenefit": nil,
191 | "promotionHintDes": nil,
192 | "propertyDynamicMeta": "grossLineTotalAfterDiscLC:T,grossLineTotalLC:T,salesUomName:T,grossLineTotalAfterDisc:T,promotionItem:T,grossProfitRate:T,netLineTotalLC:T,taxAmountLC:T,grossProfitAmount:T,salesUom:T,lineType:T,exceptionReasonDesc:T,inventoryUomQuantity:T,exceptionReason:T,originSkuPrice:T,netLineTotalAfterDisc:T,netLineTotalAfterDiscLC:T,inventoryUom:T,totalLC:T,exceptionFlag:T,totalAfterDiscountLC:T,costTotal:T,invoiceStatus:T,lineNumber:T,inventoryUomName:T,taxAmount:T,promotion:T",
193 | "purchaseOrderId": nil,
194 | "purchaseOrderLineId": nil,
195 | "quantity": "1",
196 | "remark": nil,
197 | "salesUomName": "Unit",
198 | "shippingId": nil,
199 | "shippingType": nil,
200 | "skuCode": "ProductCode13",
201 | "skuMainLogoURL": "http://internal-ci.s3.amazonaws.com/T1/2015/11/09/b09fb2bc-10f8-48fd-a4e1-e190194992a4",
202 | "skuName": "Red Silk Dress",
203 | "targetDocId": nil,
204 | "taxAmount": "0",
205 | "taxAmountLC": "0",
206 | "total": "500.00",
207 | "totalAfterDiscount": "500.00",
208 | "totalAfterDiscountLC": "500.00",
209 | "totalLC": "500.00",
210 | "unitCost": "0",
211 | "unitPrice": "500.00",
212 | "uomConversionRate": "1",
213 | "variantValues": "",
214 | "warehouseCode": "主仓库",
215 | "warehouseName": "主仓库",
216 | "docCurrency": nil,
217 | "exceptionReason": nil,
218 | "inventoryUom": map[string]interface{}{
219 | "description": "无",
220 | "id": 1,
221 | "name": "无",
222 | },
223 | "promotion": nil,
224 | "promotionItem": nil,
225 | "salesUom": map[string]interface{}{
226 | "description": "无",
227 | "id": 1,
228 | "name": "无",
229 | },
230 | "sku": map[string]interface{}{
231 | "code": "ProductCode13",
232 | "id": 57,
233 | "name": "Red Silk Dress",
234 | "product": nil,
235 | },
236 | "warehouse": map[string]interface{}{
237 | "id": 1,
238 | "whsName": "主仓库",
239 | "virtualNodes": nil,
240 | "warehouseOperators": nil,
241 | "defaulterUser": nil,
242 | },
243 | },
244 | }}
245 | }
246 |
247 | func RepoCreateOrder(order CheckoutCartPlayLoad) interface{} {
248 | var newOrder interface{}
249 |
250 | GlobalDB.Update(func(tx *bolt.Tx) error {
251 | orderBucket, err := tx.CreateBucketIfNotExists([]byte(OrderTable))
252 | if err != nil {
253 | HandleError(err)
254 | return err
255 | }
256 | var cusOrderBuck *bolt.Bucket
257 | if order.ChannelAccountId == nil {
258 | cusOrderBuck, err = orderBucket.CreateBucketIfNotExists([]byte("GUESTUSER"))
259 | } else {
260 | cusOrderBuck, err = orderBucket.CreateBucketIfNotExists([]byte(order.ChannelAccountId.(string)))
261 | }
262 | if err != nil {
263 | HandleError(err)
264 | return err
265 | }
266 | newId, _ := orderBucket.NextSequence()
267 | newOrder = GenerateOrder(order, newId)
268 |
269 | orderBytes, _ := json.Marshal(newOrder)
270 | cusOrderBuck.Put(TableId(newId).ToBytes(), orderBytes)
271 |
272 | return nil
273 | })
274 |
275 | return newOrder
276 | }
277 |
278 | func GetProductATS(ProductId TableId) int64 {
279 | var atsQua int64
280 | log.Println("Try to process ats ", GlobalDB)
281 |
282 | GlobalDB.Update(func(tx *bolt.Tx) error {
283 | b, err := tx.CreateBucketIfNotExists([]byte(SKUTable))
284 |
285 | if err != nil {
286 | HandleError(err)
287 | return err
288 | }
289 |
290 | bb, errbb := b.CreateBucketIfNotExists(ProductId.ToBytes())
291 |
292 | if errbb != nil {
293 | HandleError(errbb)
294 | return err
295 | }
296 |
297 | ats := bb.Get([]byte("ats"))
298 | if ats == nil {
299 | //new product id, need initialize the ats info
300 | bb.Put([]byte("ats"), DefaultATS.ToBytes())
301 | atsQua = int64(DefaultATS)
302 | } else {
303 | result := ToInt64FromBytes(ats)
304 | if err != nil {
305 | HandleError(err)
306 | return err
307 | }
308 | atsQua = result
309 | }
310 | return nil
311 | })
312 | return atsQua
313 | }
314 |
315 | func RepoCreateATSRsp(req *ATSReq) []ATSRsp {
316 | var rest []ATSRsp
317 |
318 | for _, atsR := range req.SkuIds {
319 |
320 | atsQua := GetProductATS(atsR)
321 | rsp := ATSRsp{
322 | SkuId: atsR,
323 | Ats: atsQua,
324 | AllowBackOrder: true,
325 | }
326 | rest = append(rest, rsp)
327 | }
328 | log.Printf("ATS Rsp %+v\n", rest)
329 | return rest
330 | }
331 |
332 | func RepoCreateRecommandationProducts(Id TableId) []TableId {
333 | var ProductId []TableId
334 |
335 | GlobalDB.Update(func(tx *bolt.Tx) error {
336 | b, err := tx.CreateBucketIfNotExists([]byte(ProductTable))
337 | if err != nil {
338 | HandleError(err)
339 | return err
340 | }
341 |
342 | c := b.Cursor()
343 | num := 0
344 |
345 | for k, _ := c.First(); k != nil && (num < RecommandProductsNum); k, _ = c.Next() {
346 | ProductId = append(ProductId, TableId(ToInt64FromBytes(k)))
347 | num++
348 | }
349 | _, errbb := b.CreateBucketIfNotExists(Id.ToBytes())
350 |
351 | if errbb != nil {
352 | HandleError(errbb)
353 | return err
354 | }
355 |
356 | return nil
357 | })
358 |
359 | return ProductId
360 | }
361 |
362 | func RepoGetCustomer(channelAccountId TableId) interface{} {
363 | var result CustomerCreate
364 | key := []byte(CustomerTable)
365 |
366 | GlobalDB.Update(func(tx *bolt.Tx) error {
367 | pb, err := tx.CreateBucketIfNotExists(key)
368 |
369 | if err != nil {
370 | HandleError(err)
371 | return err
372 | }
373 |
374 | account := pb.Get(channelAccountId.ToBytes())
375 | if account == nil {
376 | HandleError(err)
377 | return err
378 | }
379 |
380 | json.Unmarshal(account, &result)
381 | return nil
382 | })
383 |
384 | return map[string]interface{}{
385 | "id": result.AccountInfo.CustomerID,
386 | "displayName": "Jet He",
387 | "email": "update@customer.com", //result.Account,
388 | "checkDuplication": true,
389 | "creationTime": "2015-11-17T05:56:58.812Z",
390 | "creatorDisplayName": "ERP SUITE",
391 | "creditLimit": "250000",
392 | "creditBalance": "100000",
393 | "outstandingPayment": "300000",
394 | "customerCode": "3",
395 | "customerName": "Jet Jet",
396 | "customerType": "CORPORATE_CUSTOMER",
397 | "dateOfBirth": nil,
398 | "facebookAccount": nil,
399 | "fax": "22323232",
400 | "firstName": "GG",
401 | "gender": nil,
402 | "googleAccount": nil,
403 | "lastMarketingCampaign": nil,
404 | "lastName": "HH",
405 | "linkedINAccount": nil,
406 | "mainContact": nil,
407 | "marketingStatus": "Unknown",
408 | "membershipBalance": 0,
409 | "membershipId": nil,
410 | "membershipSwitchOn": false,
411 | "membershipTotalEarn": 0,
412 | "mobile": 1588888888,
413 | "ownerCode": 1,
414 | "ownerDisplayName": "ERP SUITE",
415 | "phone": "12121212",
416 | "portraitId": 0,
417 | "position": nil,
418 | "remarks": "Good job",
419 | "resetPasswordLink": nil,
420 | "socialImageUrl": nil,
421 | "stage": "SUSPECT",
422 | "status": "ACTIVE",
423 | "targetGroup": nil,
424 | "taxType": "LIABLE",
425 | "title": nil,
426 | "twitterAccount": nil,
427 | "twitterDisplayName": nil,
428 | "updateTime": "2015-11-17T05:56:58.812Z",
429 | "updatorDisplayName": "ERP SUITE",
430 | "vatRegistrationNumber": nil,
431 | "versionNum": 0,
432 | "webSite": nil,
433 | "weiboDisplayName": nil,
434 | "ext_default_UDF5": "11",
435 | "priceList": nil,
436 | "customerGroup": nil,
437 | "defaultBillToAddress": nil,
438 | "defaultShipToAddress": nil,
439 | "industry": nil,
440 | "language": nil,
441 | "linkedCustomer": map[string]interface{}{
442 | "displayName": "Jet Jet",
443 | "id": result.AccountInfo.CustomerID,
444 | "priceList": nil,
445 | "customerGroup": nil,
446 | "defaultBillToAddress": nil,
447 | "defaultShipToAddress": nil,
448 | "industry": nil,
449 | "language": nil,
450 | "linkedCustomer": nil,
451 | "membershipLevel": nil,
452 | "paymentAccount": nil,
453 | "paymentTerm": nil,
454 | "serviceLevelPlan": nil,
455 | "twitter": nil,
456 | "weibo": nil,
457 | },
458 | "membershipLevel": map[string]interface{}{
459 | "id": 1,
460 | "name": "普通会员",
461 | },
462 | "paymentAccount": map[string]interface{}{
463 | "acctName": "Cash",
464 | "id": 1,
465 | "acctType": nil,
466 | "country": nil,
467 | },
468 | "paymentTerm": map[string]interface{}{
469 | "id": 1,
470 | "name": "现金基础",
471 | },
472 | "serviceLevelPlan": map[string]interface{}{
473 | "displayName": "Gold",
474 | "id": 1,
475 | },
476 | "twitter": nil,
477 | "weibo": nil,
478 | }
479 | }
480 |
481 | func RepoCreateAccount(customer CustomerCreate) CustomerCreateRsp {
482 | var result CustomerCreateRsp
483 | key := []byte(CustomerTable)
484 |
485 | GlobalDB.Update(func(tx *bolt.Tx) error {
486 | pb, err := tx.CreateBucketIfNotExists(key)
487 |
488 | if err != nil {
489 | HandleError(err)
490 | return err
491 | }
492 |
493 | customerId, _ := pb.NextSequence()
494 |
495 | b, err := pb.CreateBucketIfNotExists([]byte(customer.Account))
496 |
497 | if err != nil {
498 | HandleError(err)
499 | return err
500 | }
501 |
502 | user := b.Get([]byte("User"))
503 | if user == nil {
504 | //create new user
505 | customer.AccountInfo.CustomerID = TableId(customerId)
506 | customer.AccountInfo.CustomerCode = "offline" + strconv.FormatInt(customer.AccountInfo.CustomerID.ToInt(), 10)
507 | customer.AccountInfo.ChannelAccountID = TableId(customer.ChannelId * customer.AccountInfo.CustomerID)
508 | //test
509 | cusStream, _ := json.Marshal(&customer)
510 | b.Put([]byte("User"), cusStream)
511 | pb.Put(customer.AccountInfo.ChannelAccountID.ToBytes(), cusStream)
512 | //customer.AccountInfo.FailType = "CUSTOMERTYPEMISSMATCH"
513 | result = customer.AccountInfo
514 | // pbaddr, err := tx.CreateBucketIfNotExists([]byte(AddressTable))
515 | // if err != nil {
516 | // HandleError(err)
517 | // return err
518 | // }
519 | // pbaddr.Put(customer.AccountInfo.CustomerID.ToBytes(), cusStream)
520 | } else {
521 | json.Unmarshal(user, &customer)
522 | result = customer.AccountInfo
523 | }
524 |
525 | return nil
526 | })
527 |
528 | return result
529 | }
530 |
531 | func RepoCreateAddress(customer *CustomerAddress) (result interface{}) {
532 | key := []byte(AddressTable)
533 |
534 | GlobalDB.Update(func(tx *bolt.Tx) error {
535 | pb, err := tx.CreateBucketIfNotExists(key)
536 |
537 | if err != nil {
538 | HandleError(err)
539 | return err
540 | }
541 |
542 | paddr, err := pb.CreateBucketIfNotExists(customer.CustomerInfo.Id.ToBytes())
543 | if err != nil {
544 | HandleError(err)
545 | return err
546 | }
547 |
548 | //create the bucket for store address info
549 | addressBucket, _ := paddr.CreateBucketIfNotExists([]byte("addresses"))
550 | addressId, _ := addressBucket.NextSequence()
551 | customer.Id = TableId(addressId)
552 |
553 | streamD, _ := json.Marshal(customer)
554 | addressBucket.Put(TableId(addressId).ToBytes(), streamD)
555 | result = customer
556 |
557 | return nil
558 | })
559 | return
560 | }
561 |
562 | func RepoUpdateAddress(addressId TableId, customer *CustomerAddress) (result interface{}) {
563 | key := []byte(AddressTable)
564 |
565 | GlobalDB.Update(func(tx *bolt.Tx) error {
566 | pb, err := tx.CreateBucketIfNotExists(key)
567 |
568 | if err != nil {
569 | HandleError(err)
570 | return err
571 | }
572 |
573 | cusBuk := pb.Bucket(customer.CustomerInfo.Id.ToBytes())
574 | if cusBuk == nil {
575 | result = "not found this user:" + customer.CustomerInfo.Id.ToString()
576 | return nil
577 | } else {
578 | addressBucket := cusBuk.Bucket([]byte("addresses"))
579 | if addressBucket == nil {
580 | result = "not found this account:" + customer.CustomerInfo.Email
581 | return nil
582 | }
583 |
584 | //create the bucket for store address info
585 | oldAddress := addressBucket.Get(addressId.ToBytes())
586 | var oldCustomerAddr CustomerAddress
587 | json.Unmarshal(oldAddress, &oldCustomerAddr)
588 |
589 | //set to new one
590 | oldCustomerAddr.AddressInfo = customer.AddressInfo
591 | oldCustomerAddr.DefaultShipTo = customer.DefaultShipTo
592 | oldCustomerAddr.DefaultBillTo = customer.DefaultBillTo
593 | streamD, _ := json.Marshal(oldCustomerAddr)
594 |
595 | addressBucket.Put(TableId(addressId).ToBytes(), streamD)
596 | result = oldCustomerAddr
597 | }
598 | return nil
599 | })
600 | return
601 | }
602 |
603 | func RepoGetCustomerAddress(customerId TableId) (result interface{}) {
604 | key := []byte(AddressTable)
605 |
606 | GlobalDB.Update(func(tx *bolt.Tx) error {
607 | pb, err := tx.CreateBucketIfNotExists(key)
608 |
609 | if err != nil {
610 | HandleError(err)
611 | return err
612 | }
613 | cusBuk := pb.Bucket(customerId.ToBytes())
614 | if cusBuk == nil {
615 | result = "not found this user:" + customerId.ToString()
616 | return nil
617 | } else {
618 |
619 | countinfo := make(map[string]interface{})
620 | var count int64 = 0
621 |
622 | //create the bucket for store address info
623 | addressBucket := cusBuk.Bucket([]byte("addresses"))
624 | if addressBucket == nil {
625 | countinfo["odata.count"] = 0
626 | result = countinfo
627 | } else {
628 | var bos []interface{}
629 | cur := addressBucket.Cursor()
630 | for k, v := cur.First(); k != nil; k, v = cur.Next() {
631 | count++
632 | var Addr CustomerAddress
633 | json.Unmarshal(v, &Addr)
634 | bos = append(bos, Addr)
635 | }
636 |
637 | countinfo["odata.count"] = 150
638 | countinfo["value"] = bos
639 | result = countinfo
640 | }
641 | }
642 | return nil
643 | })
644 | return
645 | }
646 |
--------------------------------------------------------------------------------
/offline/repo_test.go:
--------------------------------------------------------------------------------
1 | package offline_test
2 |
3 | import (
4 | "encoding/binary"
5 | "strconv"
6 | "testing"
7 | )
8 |
9 | func TestIntSliceEncode(t *testing.T) {
10 | var result []byte
11 | result = strconv.AppendInt(result, 124, 10)
12 | if string(result) != "124" {
13 | t.Errorf("%v\n", result)
14 | }
15 | result = strconv.AppendInt(result, 12, 10)
16 |
17 | another := make([]byte, 14)
18 | nul := binary.PutVarint(another, 2)
19 | nul = binary.PutVarint(another[4:], 111111111)
20 | if nul < 0 {
21 | t.Error()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/offline/sample_handler.go:
--------------------------------------------------------------------------------
1 | package offline
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "net"
7 | "net/http"
8 | "strconv"
9 | "strings"
10 |
11 | "github.com/boltdb/bolt"
12 | . "github.com/compasses/MockXServer/utils"
13 |
14 | "github.com/julienschmidt/httprouter"
15 | )
16 |
17 | //MockServerError for some error handler
18 | func MockServerError(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
19 | w.WriteHeader(500)
20 | result := map[string]interface{}{
21 | "odata.error": map[string]interface{}{
22 | "error-code": "P129S00003",
23 | "path": nil,
24 | "targetLabel": nil,
25 | "message": map[string]interface{}{
26 | "lang": "zh-CN",
27 | "value": "系统出错。我们已经收到错误通知,正在处理。",
28 | },
29 | },
30 | }
31 | log.Printf("MockServerError Rsp: %+v", result)
32 |
33 | if err := json.NewEncoder(w).Encode(result); err != nil {
34 | panic(err)
35 | }
36 | }
37 |
38 | //NotFoundHandler not found error
39 | func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
40 | w.WriteHeader(200)
41 | r.ParseForm() //解析参数,默认是不会解析的
42 |
43 | log.Println(net.ParseIP(strings.Split(r.RemoteAddr, ":")[0]))
44 | dec := json.NewDecoder(r.Body)
45 | var result interface{}
46 | dec.Decode(&result)
47 | log.Println("Req:", result)
48 | //w.Write([]byte("Welcome to compasses/MockXServer
"))
49 | w.WriteHeader(200)
50 | w.Write([]byte("[]"))
51 | }
52 |
53 | //BackupDB backupdb
54 | func BackupDB(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
55 | err := GlobalDB.View(func(tx *bolt.Tx) error {
56 | w.Header().Set("Content-Type", "application/octet-stream")
57 | w.Header().Set("Content-Disposition", `attachment; filename="ReplayDB"`)
58 | w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size())))
59 | _, err := tx.WriteTo(w)
60 | return err
61 | })
62 |
63 | if err != nil {
64 | http.Error(w, err.Error(), http.StatusInternalServerError)
65 | }
66 | }
67 |
68 | //PlaceOrder go order
69 | func PlaceOrder(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
70 | r.ParseForm()
71 | dec := json.NewDecoder(r.Body)
72 | var result CheckoutCartPlayLoad
73 | dec.Decode(&result)
74 | log.Printf("got place order request: %+v\n", result)
75 | newOrder := RepoCreateOrder(result)
76 |
77 | log.Println("PlaceOrder Rsp: ", newOrder)
78 |
79 | if err := json.NewEncoder(w).Encode(newOrder); err != nil {
80 | panic(err)
81 | }
82 | }
83 |
84 | //GetSalesOrder return sales order
85 | func GetSalesOrder(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
86 | r.ParseForm()
87 |
88 | dec := json.NewDecoder(r.Body)
89 | var req interface{}
90 | dec.Decode(&req)
91 | Req := req.(map[string]interface{})
92 | Id := TableId(ToInt64FromString(Req["orderId"].(string)))
93 |
94 | log.Println("GetSalesOrder", req)
95 | salesOrder := RepoGetSalesOrder(Id, Req["channelAccountId"].(string))
96 |
97 | if err := json.NewEncoder(w).Encode(salesOrder); err != nil {
98 | panic(err)
99 | }
100 | }
101 |
102 | func GetSalesOrders(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
103 | r.ParseForm()
104 |
105 | dec := json.NewDecoder(r.Body)
106 | var req interface{}
107 | dec.Decode(&req)
108 | Req := req.(map[string]interface{})
109 |
110 | log.Println("GetSalesOrders", req)
111 | salesOrders := RepoGetSalesOrders(Req["channelAccountId"].(string))
112 |
113 | if err := json.NewEncoder(w).Encode(salesOrders); err != nil {
114 | panic(err)
115 | }
116 | log.Println("GetSalesOrders Rsp", req)
117 |
118 | }
119 |
120 | func Checkout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
121 | r.ParseForm()
122 | dec := json.NewDecoder(r.Body)
123 | var result CheckoutCartPlayLoad
124 | err := dec.Decode(&result)
125 | if err != nil {
126 | HandleError(err)
127 | }
128 | log.Println("CheckoutShoppingCart req: ", result)
129 |
130 | resp := RepoCheckoutShoppingCart(result)
131 | log.Println("CheckoutShoppingCart resp: ", resp)
132 |
133 | if err := json.NewEncoder(w).Encode(resp); err != nil {
134 | panic(err)
135 | }
136 |
137 | }
138 |
139 | //ATS find product ATS
140 | func ATS(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
141 | r.ParseForm()
142 | dec := json.NewDecoder(r.Body)
143 | var checkInfo ATSReq
144 | err := dec.Decode(&checkInfo)
145 |
146 | var rsp interface{}
147 | log.Println("ATS request: ", checkInfo)
148 |
149 | if err != nil {
150 | HandleError(err)
151 | //w.WriteHeader(http.StatusBadRequest)
152 | log.Println("ATS just returen the default")
153 | rsp = map[string]interface{}{
154 | "allowBackOrder": true,
155 | }
156 | } else {
157 | log.Printf("ATS Req %+v\n", checkInfo)
158 | rsp = RepoCreateATSRsp(&checkInfo)
159 | }
160 | w.Header().Set("Content-Type", "application/json; charset=UTF-8")
161 | w.WriteHeader(http.StatusOK)
162 |
163 | if err := json.NewEncoder(w).Encode(rsp); err != nil {
164 | panic(err)
165 | }
166 | }
167 |
168 | func RecommandationProducts(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
169 | r.ParseForm()
170 | dec := json.NewDecoder(r.Body)
171 | var id RecommandInfo
172 | err := dec.Decode(&id)
173 |
174 | if err != nil {
175 | HandleError(err)
176 | w.WriteHeader(http.StatusBadRequest)
177 | return
178 | }
179 | log.Printf("RecommandProducts Req%+v\n", id)
180 |
181 | RecommandIds := RepoCreateRecommandationProducts(id.ProductId)
182 | w.Header().Set("Content-Type", "application/json; charset=UTF-8")
183 |
184 | w.WriteHeader(http.StatusOK)
185 |
186 | if err := json.NewEncoder(w).Encode(RecommandIds); err != nil {
187 | panic(err)
188 | }
189 | log.Printf("RecommandProducts Rsp%+v\n", RecommandIds)
190 | }
191 |
192 | func GetCustomer(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
193 | r.ParseForm()
194 | var channelAccountId interface{}
195 | dec := json.NewDecoder(r.Body)
196 |
197 | err := dec.Decode(&channelAccountId)
198 | id := channelAccountId.(map[string]interface{})
199 |
200 | if err != nil {
201 | HandleError(err)
202 | w.WriteHeader(http.StatusBadRequest)
203 | return
204 | }
205 | log.Printf("Get customer req %+v\n", channelAccountId)
206 |
207 | Account := RepoGetCustomer(GetIdFromStr(id["channelAccountId"].(string)))
208 |
209 | if err := json.NewEncoder(w).Encode(Account); err != nil {
210 | panic(err)
211 | }
212 | log.Printf("Return Customer Rsp %+v\n", Account)
213 | }
214 |
215 | func UpdateCustomer(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
216 |
217 | if err := json.NewEncoder(w).Encode(nil); err != nil {
218 | panic(err)
219 | }
220 |
221 | log.Printf("customer exist")
222 | }
223 | func CheckEmailExistence(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
224 |
225 | if err := json.NewEncoder(w).Encode(nil); err != nil {
226 | panic(err)
227 | }
228 | log.Printf("customer exist")
229 | }
230 |
231 | func CreateCustomer(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
232 | r.ParseForm()
233 | var customer CustomerCreate
234 | dec := json.NewDecoder(r.Body)
235 |
236 | err := dec.Decode(&customer)
237 |
238 | if err != nil {
239 | HandleError(err)
240 | w.WriteHeader(http.StatusBadRequest)
241 | return
242 | }
243 |
244 | log.Printf("Customer Create Req%+v\n", customer)
245 |
246 | Account := RepoCreateAccount(customer)
247 |
248 | if err := json.NewEncoder(w).Encode(Account); err != nil {
249 | panic(err)
250 | }
251 | log.Printf("Customer Create Rsp%+v\n", Account)
252 | }
253 |
254 | func CustomerAddressNew(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
255 | r.ParseForm()
256 | dec := json.NewDecoder(r.Body)
257 |
258 | var addInfo CustomerAddress
259 | err := dec.Decode(&addInfo)
260 |
261 | if err != nil {
262 | HandleError(err)
263 | w.WriteHeader(http.StatusBadRequest)
264 | return
265 | }
266 |
267 | Rs := RepoCreateAddress(&addInfo)
268 |
269 | if err = json.NewEncoder(w).Encode(Rs); err != nil {
270 | panic(err)
271 | }
272 |
273 | log.Printf("Create address info %+v\n", addInfo)
274 | log.Printf("Result %+v\n", Rs)
275 |
276 | }
277 |
278 | func CustomerAddressUpdate(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
279 | r.ParseForm()
280 | addressId := GetIdFromStr(ps.ByName("id"))
281 |
282 | dec := json.NewDecoder(r.Body)
283 |
284 | var addInfo CustomerAddress
285 | err := dec.Decode(&addInfo)
286 |
287 | if err != nil {
288 | HandleError(err)
289 | w.WriteHeader(http.StatusBadRequest)
290 | return
291 | }
292 |
293 | Rs := RepoUpdateAddress(addressId, &addInfo)
294 | log.Printf("Update address info %+v\n", Rs)
295 |
296 | if err = json.NewEncoder(w).Encode(Rs); err != nil {
297 | panic(err)
298 | }
299 | }
300 |
301 | func GetCustomerAddress(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
302 | r.ParseForm()
303 |
304 | customerId := GetIdFromStr(r.Form["$filter"][0])
305 | Rs := RepoGetCustomerAddress(customerId)
306 |
307 | if err := json.NewEncoder(w).Encode(Rs); err != nil {
308 | panic(err)
309 | }
310 | }
311 | func MiscCheck(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
312 | r.ParseForm()
313 | dec := json.NewDecoder(r.Body)
314 |
315 | var checkParam map[string]interface{}
316 | err := dec.Decode(&checkParam)
317 |
318 | if err != nil {
319 | HandleError(err)
320 | w.WriteHeader(http.StatusBadRequest)
321 | return
322 | }
323 |
324 | log.Println("misc check parames ", checkParam)
325 | Rs := RetrieveByMapLevel(checkParam, []string{"lines"})
326 | lines := Rs.([]interface{})
327 |
328 | resp := make(map[string][]interface{})
329 | for _, val := range lines {
330 | valm := val.(map[string]interface{})
331 | resp["lineResult"] = append(resp["lineResult"], map[string]interface{}{
332 | "onChannel": "true",
333 | "ats": 10,
334 | "allowBackOrder": "true",
335 | "skuId": valm["skuId"],
336 | "valid": "true",
337 | })
338 | }
339 |
340 | log.Println("resp ", resp)
341 |
342 | if err = json.NewEncoder(w).Encode(resp); err != nil {
343 | panic(err)
344 | }
345 | }
346 |
--------------------------------------------------------------------------------
/offline/sample_routes_define.go:
--------------------------------------------------------------------------------
1 | package offline
2 |
3 | import (
4 | "github.com/boltdb/bolt"
5 | "github.com/julienschmidt/httprouter"
6 | )
7 |
8 | type Route struct {
9 | Name string
10 | Method string
11 | Pattern string
12 | HandleFunc httprouter.Handle
13 | }
14 |
15 | type Routes []Route
16 |
17 | var routes = Routes{
18 | Route{
19 | "ATS Check",
20 | "POST",
21 | "/api/EshopAdapter/Product/v1/getATS",
22 | ATS,
23 | },
24 | Route{
25 | "Recommendation Products",
26 | "POST",
27 | "/api/EshopAdapter/Product/v1/getRecommendationProductIds",
28 | RecommandationProducts,
29 | },
30 | Route{
31 | "Create Customer",
32 | "POST",
33 | "/api/EshopAdapter/Customer/v1/",
34 | CreateCustomer,
35 | },
36 | Route{
37 | "check email exist",
38 | "POST",
39 | "/sbo/service/CustomerService@isEmailAccountExist",
40 | CheckEmailExistence,
41 | },
42 | Route{
43 | "Get Customer",
44 | "POST",
45 | "/sbo/service/CustomerService@getCustomer",
46 | GetCustomer,
47 | },
48 | Route{
49 | "Address New",
50 | "POST",
51 | "/sbo/service/CustomerAddressNew",
52 | CustomerAddressNew,
53 | },
54 | Route{
55 | "Update Customer",
56 | "POST",
57 | "/sbo/service/ EShopService@updateCustomer",
58 | UpdateCustomer,
59 | },
60 | Route{
61 | "Address Update",
62 | "PUT",
63 | "/sbo/service/:id/",
64 | CustomerAddressUpdate,
65 | },
66 | Route{
67 | "Address Retrieve",
68 | "GET",
69 | "/sbo/service/CustomerAddressNew/",
70 | GetCustomerAddress,
71 | },
72 | Route{
73 | "MiscCheck",
74 | "POST",
75 | "/api/EshopAdapter/Product/v1/miscCheck",
76 | MiscCheck,
77 | },
78 | Route{
79 | "Checkout",
80 | "POST",
81 | "/api/EshopAdapter/Order/v1/checkoutShoppingCart",
82 | Checkout,
83 | },
84 | Route{
85 | "PlaceOrder",
86 | "POST",
87 | "/api/EshopAdapter/Order/v1/placeOrder",
88 | PlaceOrder,
89 | },
90 | Route{
91 | "GetSalesOrder",
92 | "POST",
93 | "/sbo/service/EShopService@getSalesOrder",
94 | GetSalesOrder,
95 | },
96 | Route{
97 | "GetSalesOrders",
98 | "POST",
99 | "/sbo/service/EShopService@getSalesOrders",
100 | GetSalesOrders,
101 | },
102 | Route{
103 | "BackUpDatabase",
104 | "GET",
105 | "/backupDB",
106 | BackupDB,
107 | },
108 | // Route{
109 | // "GeneratePACT",
110 | // "GET",
111 | // "/pact",
112 | // GeneratePACT,
113 | // },
114 | // Route{
115 | // "GenerateJSON",
116 | // "GET",
117 | // "/json",
118 | // GenerateJSON,
119 | // },
120 | }
121 |
122 | func NewServerRouter(db *bolt.DB) *httprouter.Router {
123 | router := httprouter.New()
124 |
125 | for _, route := range routes {
126 | httpHandle := Logger(route.HandleFunc, route.Name)
127 |
128 | router.Handle(
129 | route.Method,
130 | route.Pattern,
131 | httpHandle,
132 | )
133 | }
134 |
135 | router.NotFound = LoggerNotFound(NotFoundHandler)
136 | GlobalDB = db
137 |
138 | return router
139 | }
140 |
--------------------------------------------------------------------------------
/online/proxyHandler.go:
--------------------------------------------------------------------------------
1 | package online
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "crypto/tls"
7 | "io/ioutil"
8 | "log"
9 | "net"
10 | "net/http"
11 | "strings"
12 | "time"
13 |
14 | "github.com/compasses/MockXServer/db"
15 | . "github.com/compasses/MockXServer/utils"
16 | )
17 |
18 | type ProxyRoute struct {
19 | client *http.Client
20 | url string
21 | db *db.ReplayDB
22 | }
23 |
24 | func NewProxyHandler(newurl string, db *db.ReplayDB) *ProxyRoute {
25 | tr := &http.Transport{
26 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
27 | DisableCompression: true,
28 | Dial: (&net.Dialer{
29 | Timeout: 30 * time.Second,
30 | KeepAlive: 30 * time.Second,
31 | }).Dial,
32 | TLSHandshakeTimeout: 10 * time.Second,
33 | }
34 |
35 | return &ProxyRoute{
36 | client: &http.Client{Transport: tr},
37 | url: newurl,
38 | db: db}
39 | }
40 |
41 | func (proxy *ProxyRoute) doReq(NeedLog bool, path, method, requestBody string, newRq *http.Request) (resp *http.Response, res []byte) {
42 | now := time.Now()
43 | resp, err := proxy.client.Do(newRq)
44 | if resp != nil {
45 | defer resp.Body.Close()
46 | }
47 |
48 | LogOutPut(NeedLog, "Time used: ", time.Since(now))
49 | if err != nil {
50 | log.Println("get error ", err)
51 | } else {
52 | if resp.Header.Get("Content-Encoding") == "gzip" {
53 | resp.Body, err = gzip.NewReader(resp.Body)
54 | if err != nil {
55 | panic(err)
56 | }
57 | }
58 |
59 | res, err = ioutil.ReadAll(resp.Body)
60 | if err != nil {
61 | log.Println("ioutil read err ", err)
62 | }
63 |
64 | if NeedLog {
65 | if resp.StatusCode == 500 || resp.StatusCode == 404 {
66 | FailNum++
67 | } else {
68 | SuccNum++
69 | err = proxy.db.StoreRequest(path, method, requestBody, string(res), resp.StatusCode)
70 | }
71 | }
72 |
73 | LogOutPut(NeedLog, "Get response : ")
74 | ResponseFormat(NeedLog, resp, string(res))
75 |
76 | if err != nil {
77 | log.Println("Store data failed ", err)
78 | }
79 | }
80 | return
81 | }
82 |
83 | func (proxy *ProxyRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) {
84 | newbody, err := ioutil.ReadAll(req.Body)
85 |
86 | if err != nil {
87 | log.Println("Read request failed..", err)
88 | return
89 | }
90 |
91 | newRq, err := http.NewRequest(req.Method, proxy.url+req.RequestURI, ioutil.NopCloser(bytes.NewReader(newbody)))
92 | if err != nil {
93 | log.Println("new request error ", err)
94 | }
95 |
96 | newRq.Header = req.Header
97 |
98 | LogOutPut(true, "online handle, New Request: ")
99 | RequstFormat(true, newRq, string(newbody))
100 | resphttp, res := proxy.doReq(true, req.RequestURI, req.Method, string(newbody), newRq)
101 | for key, _ := range resphttp.Header {
102 | w.Header().Set(key, strings.Join(resphttp.Header[key], ";"))
103 | }
104 | w.WriteHeader(resphttp.StatusCode)
105 | w.Write(res)
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/utils/helper.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "encoding/json"
7 | "log"
8 | "net/http"
9 | "reflect"
10 | "regexp"
11 | "runtime"
12 | "strconv"
13 | )
14 |
15 | type TableId int64
16 | type IDSeqs []TableId
17 | type StrSeqs []string
18 |
19 | //used to avoid recurision
20 | type idseqs IDSeqs
21 |
22 | var SuccNum int = 0
23 | var FailNum int = 0
24 |
25 | func (ids *IDSeqs) UnmarshalJSON(b []byte) (err error) {
26 | log.Println("IDSeqs got bytes: ", string(b))
27 | var tids *idseqs
28 | if err = json.Unmarshal(b, &tids); err == nil {
29 | *ids = IDSeqs(*tids)
30 | return
31 | }
32 |
33 | strid := new(StrSeqs)
34 | if err = json.Unmarshal(b, strid); err == nil {
35 | for _, val := range *strid {
36 | if v, err := strconv.ParseInt(val, 10, 64); err == nil {
37 | *ids = append(*ids, TableId(v))
38 | }
39 | }
40 | return
41 | } else {
42 | log.Println("got error: ", err)
43 | }
44 | return
45 | }
46 |
47 | func (id *TableId) UnmarshalJSON(b []byte) (err error) {
48 | log.Println("TableId got bytes: ", string(b))
49 | var tabd int64
50 | if err = json.Unmarshal(b, &tabd); err == nil {
51 | *id = TableId(tabd)
52 | return
53 | }
54 |
55 | s := ""
56 | if err = json.Unmarshal(b, &s); err == nil {
57 | v, err := strconv.ParseInt(s, 10, 64)
58 | if err == nil {
59 | *id = TableId(v)
60 | }
61 | }
62 |
63 | return
64 | }
65 |
66 | func GetAddressObj(addr interface{}) map[string]interface{} {
67 | old := addr.(map[string]interface{})
68 |
69 | var result = map[string]interface{}{
70 | "recipientName": old["userName"],
71 | "cityName": old["customCity"],
72 | "stateId": old["state"],
73 | "countryId": old["country"],
74 | "street1": old["address1"],
75 | "street2": old["address2"],
76 | "zipCode": old["zipCode"],
77 | "mobile": old["phone"],
78 | "state": old["customState"],
79 | }
80 |
81 | return result
82 | }
83 |
84 | func ToInt64FromString(input string) int64 {
85 | re, _ := strconv.ParseInt(input, 10, 8)
86 | return re
87 | }
88 |
89 | func ToFloat64FromString(input string) float64 {
90 | re, _ := strconv.ParseFloat(input, 8)
91 | return re
92 | }
93 |
94 | func GetSliceIntFromBytes(input []byte) []TableId {
95 | sizeofInt := 8
96 | data := make([]TableId, len(input)/sizeofInt)
97 | buf := bytes.NewBuffer(input)
98 | for i := range data {
99 | var re int64
100 | binary.Read(buf, binary.LittleEndian, &re)
101 | data[i] = TableId(re)
102 | }
103 |
104 | return data
105 | }
106 |
107 | func GetSliceBytesFromInts(input []TableId) []byte {
108 | buf := new(bytes.Buffer)
109 |
110 | for i := range input {
111 | binary.Write(buf, binary.LittleEndian, int64(input[i]))
112 | }
113 | return buf.Bytes()
114 | }
115 |
116 | func ContainsIntSlice(s []TableId, e TableId) bool {
117 | for _, a := range s {
118 | if a == e {
119 | return true
120 | }
121 | }
122 | return false
123 | }
124 |
125 | func (tId TableId) ToBytes() []byte {
126 | buf := new(bytes.Buffer)
127 | binary.Write(buf, binary.LittleEndian, int64(tId))
128 | return buf.Bytes()
129 | }
130 |
131 | func (tId TableId) ToString() (str string) {
132 | str = strconv.FormatInt(int64(tId), 10)
133 | return
134 | }
135 |
136 | func (tId TableId) ToInt() int64 {
137 | return int64(tId)
138 | }
139 |
140 | func ToInt64FromBytes(st []byte) int64 {
141 | buf := bytes.NewReader(st)
142 | var result int64
143 | binary.Read(buf, binary.LittleEndian, &result)
144 | return result
145 | }
146 |
147 | //proc string like "createcustomernew(1)", and return 1
148 | func GetIdFromStr(input string) TableId {
149 | valId := regexp.MustCompile(`(\d+)`)
150 | val, _ := strconv.Atoi(valId.FindString(input))
151 | return TableId(val)
152 | }
153 |
154 | func RetrieveByMapLevel(f interface{}, levels []string) interface{} {
155 | length := len(levels)
156 |
157 | if length <= 0 {
158 | return f
159 | }
160 |
161 | result := f.(map[string]interface{})
162 |
163 | for k, v := range result {
164 | if k == levels[0] {
165 | return RetrieveByMapLevel(v.(interface{}), levels[1:])
166 | }
167 | }
168 |
169 | return result
170 | }
171 |
172 | func HandleDecodeData(f map[string]interface{}) {
173 | for k, v := range f {
174 | log.Println("key", k)
175 | switch vv := v.(type) {
176 | // case string:
177 | // log.Println(k, "is string", vv)
178 | // case int:
179 | // log.Println(k, "is int", vv)
180 | // case []interface{}:
181 | // log.Println(k, "is an array:")
182 | // for i, u := range vv {
183 | // log.Println(i, u)
184 | // }
185 | // case interface{}:
186 | // log.Println(k, "is an interface")
187 | // HandleDecodeData(f[k].(map[string]interface{}))
188 | default:
189 | log.Println(vv)
190 | log.Println(k, "is of a type I don't know how to handle")
191 | }
192 | }
193 | }
194 |
195 | func HandleError(err error) {
196 | _, file, line, ok := runtime.Caller(1)
197 | if !ok {
198 | file = "???"
199 | line = 0
200 | }
201 | log.Println(err, file, "line:", line)
202 | }
203 |
204 | func LogOutPut(NeedLog bool, v ...interface{}) {
205 | if NeedLog {
206 | log.Println(v)
207 | }
208 | }
209 |
210 | func RequstFormat(NeedLog bool, req *http.Request, newbody string) {
211 | if !NeedLog {
212 | return
213 | }
214 | result := "URL: " + req.URL.String() + "\r\n"
215 | result += "Method: " + req.Method + "\r\n"
216 | result += "Body: " + newbody + "\r\n"
217 | result += "Header: "
218 | for key, _ := range req.Header {
219 | var vals string = ""
220 | for _, allV := range req.Header[key] {
221 | vals += allV
222 | }
223 | result += " Key: " + key + " -> " + vals + "\r\n"
224 | }
225 | LogOutPut(NeedLog, result)
226 | }
227 |
228 | func ResponseFormat(NeedLog bool, resp *http.Response, body string) {
229 | if !NeedLog {
230 | return
231 | }
232 |
233 | result := "Status: " + resp.Status + "\r\n"
234 | result += "Body: " + body + "\r\n"
235 | result += "Header: "
236 | for key, _ := range resp.Header {
237 | var vals string = ""
238 | for _, allV := range resp.Header[key] {
239 | vals += allV
240 | }
241 | result += " Key: " + key + " -> " + vals + "\r\n"
242 | }
243 | LogOutPut(NeedLog, result)
244 | }
245 |
246 | func ReflectStruct(req *http.Request) {
247 | s := reflect.ValueOf(req).Elem()
248 | typeOfT := s.Type()
249 | for i := 0; i < s.NumField(); i++ {
250 | f := s.Field(i)
251 | log.Printf("%d: %s %s = %v\n", i,
252 | typeOfT.Field(i).Name, f.Type(), f.Interface())
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/utils/helper_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bytes"
5 | "crypto/tls"
6 | "database/sql"
7 | "encoding/binary"
8 | "encoding/json"
9 | "fmt"
10 | "io/ioutil"
11 | "log"
12 | "net/http"
13 | "os"
14 | "regexp"
15 | "strconv"
16 | "strings"
17 | "testing"
18 |
19 | "github.com/franela/goreq"
20 |
21 | "golang.org/x/text/encoding"
22 | "golang.org/x/text/encoding/simplifiedchinese"
23 | "golang.org/x/text/transform"
24 | )
25 |
26 | func TestBytesToInt(t *testing.T) {
27 | test1 := int64(256)
28 | buf := new(bytes.Buffer)
29 | binary.Write(buf, binary.LittleEndian, test1)
30 | //binary.PutVarint(buf.Bytes(), int64(test1))
31 | fmt.Println("byte is ", buf.Bytes())
32 | bufr := bytes.NewBuffer(buf.Bytes())
33 | var rest int64
34 | err := binary.Read(bufr, binary.LittleEndian, &rest)
35 | if err != nil {
36 | t.Error(err)
37 | }
38 | fmt.Println("rest value ", rest)
39 |
40 | num, _ := binary.Varint(buf.Bytes())
41 | fmt.Println("num is ", num)
42 |
43 | var testS []TableId
44 | testS = append(testS, 34, 3443, 344242)
45 | tids := GetSliceBytesFromInts(testS)
46 | fmt.Println("length is ", len(tids), "val is ", tids)
47 |
48 | tidr := GetSliceIntFromBytes(tids)
49 | fmt.Println("int value is ", tidr)
50 |
51 | t4 := ToInt64FromBytes(tids[0:8])
52 | fmt.Println("t4 val is ", t4)
53 |
54 | }
55 |
56 | func TestBytesToInt2(t *testing.T) {
57 | var intVar = 123
58 | fmt.Println("intVar is : ", intVar)
59 |
60 | intByte := []byte(strconv.Itoa(intVar))
61 |
62 | fmt.Println("intByte is : ", intByte)
63 | }
64 |
65 | func TestGetIntFromStr(t *testing.T) {
66 | input := "CreateCustomerNew(23)"
67 | valId := regexp.MustCompile(`(\d+)`)
68 | val, _ := strconv.Atoi(valId.FindString(input))
69 | fmt.Println("id :", val)
70 | var ni []interface{}
71 | ni = append(ni, "ok")
72 | ni = append(ni, 2323)
73 |
74 | fmt.Println("interfaces values ", ni)
75 | }
76 |
77 | func TestHttpGet(t *testing.T) {
78 | res, err := goreq.Request{
79 | Method: "GET",
80 | Uri: "https://cnpvgvb1ep052.pvgl.sap.corp:29900",
81 | ShowDebug: true,
82 | }.Do()
83 |
84 | // dec := json.NewDecoder(res.Response.Body)
85 | // var result interface{}
86 | // dec.Decode(&result)
87 |
88 | // //fmt.Println("Result ", res.Response)
89 | // fmt.Println("resp result ", result)
90 | fmt.Println("error is ", err)
91 | if err == nil {
92 | nres1, _ := ioutil.ReadAll(res.Response.Body)
93 | fmt.Println("raw body is ", string(nres1))
94 | }
95 |
96 | tr := &http.Transport{
97 | TLSClientConfig: &tls.Config{},
98 | DisableCompression: true,
99 | }
100 | client := &http.Client{Transport: tr}
101 | resp, err := client.Get("https://www.baidu.com")
102 | fmt.Println("err is ", err)
103 | defer resp.Body.Close()
104 | dec := json.NewDecoder(resp.Body)
105 | var result2 interface{}
106 | err = dec.Decode(&result2)
107 | fmt.Println("decode error ", err)
108 | fmt.Println("body is ", result2)
109 | nres, _ := ioutil.ReadAll(resp.Body)
110 | fmt.Println("raw body is ", string(nres))
111 |
112 | }
113 |
114 | func TestDBCase(t *testing.T) {
115 | db, err := sql.Open("mysql", "root:12345@tcp(cnpvgvb1ep140.pvgl.sap.corp:3306)/")
116 | if err != nil {
117 | panic(err.Error()) // Just for example purpose. You should use proper error handling instead of panic
118 | }
119 | defer db.Close()
120 |
121 | // Open doesn't open a connection. Validate DSN data:
122 | err = db.Ping()
123 | if err != nil {
124 | panic(err.Error()) // proper error handling instead of panic in your app
125 | }
126 |
127 | // Select the DataBase
128 | db.Exec("use ESHOPDB116")
129 | // Execute the query
130 | // rows, err := db.Query("show databases")
131 | // if err != nil {
132 | // panic(err.Error()) // proper error handling instead of panic in your app
133 | // }
134 | rows, err := db.Query("select option_value from wp_options where option_name = 'eshopSetting'")
135 | // Get column names
136 | // columns, err := rows.Columns()
137 | // fmt.Println("columns: ", columns)
138 |
139 | if err != nil {
140 | panic(err.Error()) // proper error handling instead of panic in your app
141 | }
142 |
143 | // // Make a slice for the values
144 | // values := make([]sql.RawBytes, 1)
145 |
146 | // // rows.Scan wants '[]interface{}' as an argument, so we must copy the
147 | // // references into such a slice
148 | // // See http://code.google.com/p/go-wiki/wiki/InterfaceSlice for details
149 | // scanArgs := make([]interface{}, len(values))
150 | // for i := range values {
151 | // scanArgs[i] = &values[i]
152 | // }
153 | // fmt.Printf("scanArgs: %v\n", values)
154 | var res []byte
155 | // Fetch rows
156 | for rows.Next() {
157 | // get RawBytes from data
158 | err = rows.Scan(&res)
159 |
160 | var dataS interface{}
161 | json.Unmarshal(res, &dataS)
162 | m := dataS.(map[string]interface{})
163 | fmt.Println(": ", m["shopName"])
164 | if err != nil {
165 | panic(err.Error()) // proper error handling instead of panic in your app
166 | }
167 |
168 | // Now do something with the data.
169 | // // Here we just print each column as a string.
170 | // var value string
171 | // for _, col := range values {
172 | // // Here we can check if the value is nil (NULL value)
173 | // if col == nil {
174 | // value = "NULL"
175 | // } else {
176 | // value = string(col)
177 | // }
178 |
179 | // }
180 | // fmt.Println("-----------------------------------")
181 | }
182 | if err = rows.Err(); err != nil {
183 | panic(err.Error()) // proper error handling instead of panic in your app
184 | }
185 | }
186 |
187 | func load(direction string, enc encoding.Encoding) (func() transform.Transformer, error) {
188 |
189 | newTransformer := enc.NewEncoder
190 | if direction == "Decode" {
191 | newTransformer = enc.NewDecoder
192 | }
193 |
194 | return newTransformer, nil
195 | }
196 |
197 | func TestConversion(t *testing.T) {
198 | f, err := os.OpenFile("testlogfile", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
199 | if err != nil {
200 | t.Fatalf("error opening file: %v", err)
201 | }
202 | defer f.Close()
203 |
204 | log.SetOutput(f)
205 | log.Println("This is a test log entry")
206 |
207 | sr := strings.NewReader("你好,世界")
208 |
209 | newTransformer, _ := load("Decode", simplifiedchinese.GBK)
210 |
211 | rInUTF8 := transform.NewReader(sr, newTransformer())
212 | res := make([]byte, 100)
213 | rInUTF8.Read(res)
214 |
215 | log.Println("t is ", "真的吗", string(res))
216 | }
217 |
--------------------------------------------------------------------------------
/utils/jsonrelated.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | func TOJsonInterface(body []byte) (result interface{}, err error) {
12 | err = json.Unmarshal(body, &result)
13 | if err != nil {
14 | log.Println("Unmarshal failed ", err, body)
15 | }
16 | return
17 | }
18 |
19 | func JsonInterfaceToByte(body interface{}) (result []byte) {
20 | var err error
21 | result, err = json.Marshal(body)
22 | if err != nil {
23 | log.Println("Marshal failed ", err, body)
24 | }
25 | return
26 | }
27 |
28 | func JsonInterfaceToByteByNumber(body []byte) (result []byte) {
29 | d := json.NewDecoder(strings.NewReader(string(body)))
30 | d.UseNumber()
31 | var x interface{}
32 | if err := d.Decode(&x); err != nil {
33 | log.Fatal(err)
34 | }
35 | fmt.Printf("decoded to %#v\n", x)
36 | result, err := json.Marshal(x)
37 | if err != nil {
38 | log.Fatal(err)
39 | }
40 | return
41 | }
42 |
43 | func JsonNormalizeSingle(body string) (stream []byte, err error) {
44 | streamMap, err := TOJsonInterface([]byte(body))
45 | stream, err = json.Marshal(streamMap)
46 | if err != nil {
47 | log.Println("Marshal failed ", err, streamMap)
48 | }
49 | return
50 | }
51 |
52 | func JsonNormalize(reqBody, respBody string, statusCode int) (reqStram, respStram []byte, err error) {
53 | // reqMap, err := TOJsonInterface([]byte(reqBody))
54 | // respMap, err := TOJsonInterface([]byte(respBody))
55 |
56 | rspStream := map[string]string{
57 | strconv.Itoa(statusCode): respBody,
58 | }
59 |
60 | respStram, err = json.Marshal(rspStream)
61 | if err != nil {
62 | log.Println("Marshal failed ", err, rspStream)
63 | }
64 |
65 | reqStram = []byte(reqBody)
66 | //
67 | // reqStram, err = json.Marshal(reqMap)
68 | // if err != nil {
69 | // log.Println("Marshal failed ", err, reqMap)
70 | // }
71 |
72 | return
73 | }
74 |
--------------------------------------------------------------------------------
/version.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | const (
4 | Version = "2016-06-22 19:52:35 +0800 @8224127"
5 | Compile = "2016-06-24 14:27:22 +0800 by go version go1.6.2 darwin/amd64"
6 | )
7 |
--------------------------------------------------------------------------------
/workflow.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/compasses/MockXServer/1169543ae7d372309fc9dbbdf9aaba32a7b95c91/workflow.PNG
--------------------------------------------------------------------------------