dangogh closed pull request #1956: Added CRUD for /types
URL: https://github.com/apache/incubator-trafficcontrol/pull/1956
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/lib/go-tc/types.go b/lib/go-tc/types.go
index fc8e32acf7..d8887fc502 100644
--- a/lib/go-tc/types.go
+++ b/lib/go-tc/types.go
@@ -19,15 +19,25 @@ package tc
  * under the License.
  */
 
-// TypeResponse ...
-type TypeResponse struct {
+// TypesResponse ...
+type TypesResponse struct {
        Response []Type `json:"response"`
 }
 
 // Type contains information about a given Type in Traffic Ops.
 type Type struct {
-       ID          int    `json:"id"`
-       Name        string `json:"name,omitempty"`
-       Description string `json:"description,omitempty"`
-       UseInTable  string `json:"useInTable,omitempty"`
+       ID          int       `json:"id"`
+       LastUpdated TimeNoMod `json:"lastUpdated"`
+       Name        string    `json:"name"`
+       Description string    `json:"description"`
+       UseInTable  string    `json:"useInTable"`
+}
+
+// TypeNullable contains information about a given Type in Traffic Ops.
+type TypeNullable struct {
+       ID          *int       `json:"id" db:"id"`
+       LastUpdated *TimeNoMod `json:"lastUpdated" db:"last_updated"`
+       Name        *string    `json:"name" db:"name"`
+       Description *string    `json:"description" db:"description"`
+       UseInTable  *string    `json:"useInTable" db:"use_in_table"`
 }
diff --git a/traffic_ops/client/v13/type.go b/traffic_ops/client/v13/type.go
new file mode 100644
index 0000000000..0c95d0ab66
--- /dev/null
+++ b/traffic_ops/client/v13/type.go
@@ -0,0 +1,132 @@
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package v13
+
+import (
+       "encoding/json"
+       "fmt"
+       "net"
+       "net/http"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+)
+
+const (
+       API_v13_Types = "/api/1.3/types"
+)
+
+// Create a Type
+func (to *Session) CreateType(typ tc.Type) (tc.Alerts, ReqInf, error) {
+
+       var remoteAddr net.Addr
+       reqBody, err := json.Marshal(typ)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       resp, remoteAddr, err := to.request(http.MethodPost, API_v13_Types, 
reqBody)
+       if err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       defer resp.Body.Close()
+       var alerts tc.Alerts
+       err = json.NewDecoder(resp.Body).Decode(&alerts)
+       return alerts, reqInf, nil
+}
+
+// Update a Type by ID
+func (to *Session) UpdateTypeByID(id int, typ tc.Type) (tc.Alerts, ReqInf, 
error) {
+
+       var remoteAddr net.Addr
+       reqBody, err := json.Marshal(typ)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       route := fmt.Sprintf("%s/%d", API_v13_Types, id)
+       resp, remoteAddr, err := to.request(http.MethodPut, route, reqBody)
+       if err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       defer resp.Body.Close()
+       var alerts tc.Alerts
+       err = json.NewDecoder(resp.Body).Decode(&alerts)
+       return alerts, reqInf, nil
+}
+
+// Returns a list of Types
+func (to *Session) GetTypes() ([]tc.Type, ReqInf, error) {
+       resp, remoteAddr, err := to.request(http.MethodGet, API_v13_Types, nil)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return nil, reqInf, err
+       }
+       defer resp.Body.Close()
+
+       var data tc.TypesResponse
+       err = json.NewDecoder(resp.Body).Decode(&data)
+       return data.Response, reqInf, nil
+}
+
+// GET a Type by the Type ID
+func (to *Session) GetTypeByID(id int) ([]tc.Type, ReqInf, error) {
+       route := fmt.Sprintf("%s/%d", API_v13_Types, id)
+       resp, remoteAddr, err := to.request(http.MethodGet, route, nil)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return nil, reqInf, err
+       }
+       defer resp.Body.Close()
+
+       var data tc.TypesResponse
+       if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
+               return nil, reqInf, err
+       }
+
+       return data.Response, reqInf, nil
+}
+
+// GET a Type by the Type name
+func (to *Session) GetTypeByName(name string) ([]tc.Type, ReqInf, error) {
+       url := fmt.Sprintf("%s?name=%s", API_v13_Types, name)
+       resp, remoteAddr, err := to.request(http.MethodGet, url, nil)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return nil, reqInf, err
+       }
+       defer resp.Body.Close()
+
+       var data tc.TypesResponse
+       if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
+               return nil, reqInf, err
+       }
+
+       return data.Response, reqInf, nil
+}
+
+// DELETE a Type by ID
+func (to *Session) DeleteTypeByID(id int) (tc.Alerts, ReqInf, error) {
+       route := fmt.Sprintf("%s/%d", API_v13_Types, id)
+       resp, remoteAddr, err := to.request(http.MethodDelete, route, nil)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       defer resp.Body.Close()
+       var alerts tc.Alerts
+       err = json.NewDecoder(resp.Body).Decode(&alerts)
+       return alerts, reqInf, nil
+}
diff --git a/traffic_ops/testing/api/todb/todb.go 
b/traffic_ops/testing/api/todb/todb.go
index d442cd01c3..ff25a8c100 100644
--- a/traffic_ops/testing/api/todb/todb.go
+++ b/traffic_ops/testing/api/todb/todb.go
@@ -80,11 +80,11 @@ func SetupTestData(cfg *config.Config, db *sql.DB) error {
                os.Exit(1)
        }
 
-       err = SetupTypes(cfg, db)
-       if err != nil {
-               fmt.Printf("\nError setting up type %s - %s, %v\n", 
cfg.TrafficOps.URL, cfg.TrafficOps.User, err)
-               os.Exit(1)
-       }
+       //err = SetupTypes(cfg, db)
+       //if err != nil {
+       //fmt.Printf("\nError setting up type %s - %s, %v\n", 
cfg.TrafficOps.URL, cfg.TrafficOps.User, err)
+       //os.Exit(1)
+       //}
 
        err = SetupDivisions(cfg, db)
        if err != nil {
diff --git a/traffic_ops/testing/api/v13/tc-fixtures.json 
b/traffic_ops/testing/api/v13/tc-fixtures.json
index 33f2459ee2..7000af9423 100644
--- a/traffic_ops/testing/api/v13/tc-fixtures.json
+++ b/traffic_ops/testing/api/v13/tc-fixtures.json
@@ -583,5 +583,187 @@
             "name": "tenant2",
             "parentTenantName": "root"
         }
+    ],
+    "types": [
+        {
+            "description": "Host header regular expression",
+            "lastUpdated": "2018-03-02T19:13:46.788583+00:00",
+            "name": "HOST_REGEXP",
+            "useInTable": "regex"
+        },
+        {
+            "description": "DNS Content routing, RAM cache, National",
+            "lastUpdated": "2018-03-02T19:13:46.792319+00:00",
+            "name": "DNS_LIVE_NATNL",
+            "useInTable": "deliveryservice"
+        },
+        {
+            "description": "Other CDN (CDS-IS, Akamai, etc)",
+            "lastUpdated": "2018-03-02T19:13:46.793921+00:00",
+            "name": "OTHER_CDN",
+            "useInTable": "server"
+        },
+        {
+            "description": "Client-Controlled Steering Delivery Service",
+            "lastUpdated": "2018-03-02T19:13:46.795291+00:00",
+            "name": "CLIENT_STEERING",
+            "useInTable": "deliveryservice"
+        },
+        {
+            "description": "influxdb type",
+            "lastUpdated": "2018-03-02T19:13:46.796707+00:00",
+            "name": "INFLUXDB",
+            "useInTable": "server"
+        },
+        {
+            "description": "riak type",
+            "lastUpdated": "2018-03-02T19:13:46.798008+00:00",
+            "name": "RIAK",
+            "useInTable": "server"
+        },
+        {
+            "description": "Origin",
+            "lastUpdated": "2018-03-02T19:13:46.799404+00:00",
+            "name": "ORG",
+            "useInTable": "server"
+        },
+        {
+            "description": "HTTP Content routing cache in RAM ",
+            "lastUpdated": "2018-03-02T19:13:46.800738+00:00",
+            "name": "HTTP_LIVE",
+            "useInTable": "deliveryservice"
+        },
+        {
+            "description": "Active Directory User",
+            "lastUpdated": "2018-03-02T19:13:46.802044+00:00",
+            "name": "ACTIVE_DIRECTORY",
+            "useInTable": "tm_user"
+        },
+        {
+            "description": "federation type resolve4",
+            "lastUpdated": "2018-03-02T19:13:46.803471+00:00",
+            "name": "RESOLVE4",
+            "useInTable": "federation"
+        },
+        {
+            "description": "Static DNS A entry",
+            "lastUpdated": "2018-03-02T19:13:46.804776+00:00",
+            "name": "A_RECORD",
+            "useInTable": "staticdnsentry"
+        },
+        {
+            "description": "Local User",
+            "lastUpdated": "2018-03-02T19:13:46.806035+00:00",
+            "name": "LOCAL",
+            "useInTable": "tm_user"
+        },
+        {
+            "description": "Weighted steering target",
+            "lastUpdated": "2018-03-02T19:13:46.80748+00:00",
+            "name": "STEERING_WEIGHT",
+            "useInTable": "steering_target"
+        },
+        {
+            "description": "HTTP Content routing, RAM cache, National",
+            "lastUpdated": "2018-03-02T19:13:46.808911+00:00",
+            "name": "HTTP_LIVE_NATNL",
+            "useInTable": "deliveryservice"
+        },
+        {
+            "description": "Ops hosts for management",
+            "lastUpdated": "2018-03-02T19:13:46.810576+00:00",
+            "name": "TOOLS_SERVER",
+            "useInTable": "server"
+        },
+        {
+            "description": "Path regular expression",
+            "lastUpdated": "2018-03-02T19:13:46.812049+00:00",
+            "name": "PATH_REGEXP",
+            "useInTable": "regex"
+        },
+        {
+            "description": "Static DNS CNAME entry",
+            "lastUpdated": "2018-03-02T19:13:46.813461+00:00",
+            "name": "CNAME_RECORD",
+            "useInTable": "staticdnsentry"
+        },
+        {
+            "description": "Kabletown Content Router",
+            "lastUpdated": "2018-03-02T19:13:46.814833+00:00",
+            "name": "CCR",
+            "useInTable": "server"
+        },
+        {
+            "description": "Mid Cachegroup",
+            "lastUpdated": "2018-03-02T19:13:46.816199+00:00",
+            "name": "MID_LOC",
+            "useInTable": "cachegroup"
+        },
+        {
+            "description": "Edge Cache",
+            "lastUpdated": "2018-03-02T19:13:46.817689+00:00",
+            "name": "EDGE",
+            "useInTable": "server"
+        },
+        {
+            "description": "Ordered steering target",
+            "lastUpdated": "2018-03-02T19:13:46.81913+00:00",
+            "name": "STEERING_ORDER",
+            "useInTable": "steering_target"
+        },
+        {
+            "description": "DNS Content Routing",
+            "lastUpdated": "2018-03-02T19:13:46.820528+00:00",
+            "name": "DNS",
+            "useInTable": "deliveryservice"
+        },
+        {
+            "description": "federation type resolve6",
+            "lastUpdated": "2018-03-02T19:13:46.822161+00:00",
+            "name": "RESOLVE6",
+            "useInTable": "federation"
+        },
+        {
+            "description": "Static DNS AAAA entry",
+            "lastUpdated": "2018-03-02T19:13:46.823506+00:00",
+            "name": "AAAA_RECORD",
+            "useInTable": "staticdnsentry"
+        },
+        {
+            "description": "HTTP Content Routing, no caching",
+            "lastUpdated": "2018-03-02T19:13:46.824798+00:00",
+            "name": "HTTP_NO_CACHE",
+            "useInTable": "deliveryservice"
+        },
+        {
+            "description": "any_map type",
+            "lastUpdated": "2018-03-02T19:13:46.826411+00:00",
+            "name": "ANY_MAP",
+            "useInTable": "deliveryservice"
+        },
+        {
+            "description": "Steering Delivery Service",
+            "lastUpdated": "2018-03-02T19:13:46.827779+00:00",
+            "name": "STEERING",
+            "useInTable": "deliveryservice"
+        },
+        {
+            "description": "Edge Cachegroup",
+            "lastUpdated": "2018-03-02T19:13:46.829249+00:00",
+            "name": "EDGE_LOC",
+            "useInTable": "cachegroup"
+        },
+        {
+            "description": "HTTP Content routing cache ",
+            "lastUpdated": "2018-03-02T19:13:46.830862+00:00",
+            "name": "HTTP",
+            "useInTable": "deliveryservice"
+        },
+        {
+            "description": "Mid Tier Cache",
+            "lastUpdated": "2018-03-02T19:13:46.832327+00:00",
+            "name": "MID",
+            "useInTable": "server"
+        }
     ]
 }
diff --git a/traffic_ops/testing/api/v13/traffic_control.go 
b/traffic_ops/testing/api/v13/traffic_control.go
index 3977c48ce5..70c8d28180 100644
--- a/traffic_ops/testing/api/v13/traffic_control.go
+++ b/traffic_ops/testing/api/v13/traffic_control.go
@@ -30,4 +30,5 @@ type TrafficControl struct {
        Regions                 []tcapi.Region                 `json:"regions"`
        Statuses                []tcapi.Status                 `json:"statuses"`
        Tenants                 []tcapi.Tenant                 `json:"tenants"`
+       Types                   []tcapi.Type                   `json:"types"`
 }
diff --git a/traffic_ops/testing/api/v13/types_test.go 
b/traffic_ops/testing/api/v13/types_test.go
new file mode 100644
index 0000000000..764501a855
--- /dev/null
+++ b/traffic_ops/testing/api/v13/types_test.go
@@ -0,0 +1,109 @@
+package v13
+
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+       "testing"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-log"
+       tc "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+)
+
+func TestTypes(t *testing.T) {
+
+       CreateTestTypes(t)
+       UpdateTestTypes(t)
+       //GetTestTypes(t)
+       //DeleteTestTypes(t)
+
+}
+
+func CreateTestTypes(t *testing.T) {
+
+       for _, typ := range testData.Types {
+               resp, _, err := TOSession.CreateType(typ)
+               log.Debugln("Response: ", resp)
+               if err != nil {
+                       t.Errorf("could not CREATE types: %v\n", err)
+               }
+       }
+
+}
+
+func UpdateTestTypes(t *testing.T) {
+
+       firstType := testData.Types[0]
+       // Retrieve the Type by name so we can get the id for the Update
+       resp, _, err := TOSession.GetTypeByName(firstType.Name)
+       if err != nil {
+               t.Errorf("cannot GET Type by name: %v - %v\n", firstType.Name, 
err)
+       }
+       remoteType := resp[0]
+       expectedTypeName := "testType1"
+       remoteType.Name = expectedTypeName
+       var alert tc.Alerts
+       alert, _, err = TOSession.UpdateTypeByID(remoteType.ID, remoteType)
+       if err != nil {
+               t.Errorf("cannot UPDATE Type by id: %v - %v\n", err, alert)
+       }
+
+       // Retrieve the Type to check Type name got updated
+       resp, _, err = TOSession.GetTypeByID(remoteType.ID)
+       if err != nil {
+               t.Errorf("cannot GET Type by name: %v - %v\n", firstType.Name, 
err)
+       }
+       respType := resp[0]
+       if respType.Name != expectedTypeName {
+               t.Errorf("results do not match actual: %s, expected: %s\n", 
respType.Name, expectedTypeName)
+       }
+
+}
+
+func GetTestTypes(t *testing.T) {
+
+       for _, typ := range testData.Types {
+               resp, _, err := TOSession.GetTypeByName(typ.Name)
+               if err != nil {
+                       t.Errorf("cannot GET Type by name: %v - %v\n", err, 
resp)
+               }
+       }
+}
+
+func DeleteTestTypes(t *testing.T) {
+
+       for _, typ := range testData.Types {
+               // Retrieve the Type by name so we can get the id for the Update
+               resp, _, err := TOSession.GetTypeByName(typ.Name)
+               if err != nil {
+                       t.Errorf("cannot GET Type by name: %v - %v\n", 
typ.Name, err)
+               }
+               respType := resp[0]
+
+               delResp, _, err := TOSession.DeleteTypeByID(respType.ID)
+               if err != nil {
+                       t.Errorf("cannot DELETE Type by name: %v - %v\n", err, 
delResp)
+               }
+
+               // Retrieve the Type to see if it got deleted
+               types, _, err := TOSession.GetTypeByName(typ.Name)
+               if err != nil {
+                       t.Errorf("error deleting Type name: %s\n", err.Error())
+               }
+               if len(types) > 0 {
+                       t.Errorf("expected Type name: %s to be deleted\n", 
typ.Name)
+               }
+       }
+}
diff --git a/traffic_ops/traffic_ops_golang/routes.go 
b/traffic_ops/traffic_ops_golang/routes.go
index 84c7dc0e2f..7c7df779f3 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -40,6 +40,7 @@ import (
        
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/region"
        
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/status"
        
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/systeminfo"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/types"
        "github.com/basho/riak-go-client"
 )
 
@@ -122,6 +123,13 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                {1.3, http.MethodPost, `statuses/?$`, 
api.CreateHandler(status.GetRefType(), d.DB), auth.PrivLevelOperations, 
Authenticated, nil},
                {1.3, http.MethodDelete, `statuses/{id}$`, 
api.DeleteHandler(status.GetRefType(), d.DB), auth.PrivLevelOperations, 
Authenticated, nil},
 
+               //Types
+               {1.3, http.MethodGet, `types/?(\.json)?$`, 
api.ReadHandler(types.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
+               {1.3, http.MethodGet, `types/{id}$`, 
api.ReadHandler(types.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
+               {1.3, http.MethodPut, `types/{id}$`, 
api.UpdateHandler(types.GetRefType(), d.DB), auth.PrivLevelOperations, 
Authenticated, nil},
+               {1.3, http.MethodPost, `types/?$`, 
api.CreateHandler(types.GetRefType(), d.DB), auth.PrivLevelOperations, 
Authenticated, nil},
+               {1.3, http.MethodDelete, `types/{id}$`, 
api.DeleteHandler(types.GetRefType(), d.DB), auth.PrivLevelOperations, 
Authenticated, nil},
+
                //Divisions
                {1.3, http.MethodGet, `divisions/?(\.json)?$`, 
api.ReadHandler(division.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
                {1.3, http.MethodGet, `divisions/{id}$`, 
api.ReadHandler(division.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
diff --git a/traffic_ops/traffic_ops_golang/types/types.go 
b/traffic_ops/traffic_ops_golang/types/types.go
new file mode 100644
index 0000000000..41811b40f9
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/types/types.go
@@ -0,0 +1,339 @@
+package types
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "errors"
+       "fmt"
+       "strconv"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-log"
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tovalidate"
+       validation "github.com/go-ozzo/ozzo-validation"
+       "github.com/jmoiron/sqlx"
+       "github.com/lib/pq"
+)
+
+//we need a type alias to define functions on
+type TOType tc.TypeNullable
+
+//the refType is passed into the handlers where a copy of its type is used to 
decode the json.
+var refType = TOType(tc.TypeNullable{})
+
+func GetRefType() *TOType {
+       return &refType
+}
+
+//Implementation of the Identifier, Validator interface functions
+func (typ *TOType) GetID() (int, bool) {
+       if typ.ID == nil {
+               return 0, false
+       }
+       return *typ.ID, true
+}
+
+func (typ *TOType) GetAuditName() string {
+       if typ.Name != nil {
+               return *typ.Name
+       }
+       if typ.ID != nil {
+               return strconv.Itoa(*typ.ID)
+       }
+       return "unknown"
+}
+
+func (typ *TOType) GetType() string {
+       return "type"
+}
+
+func (typ *TOType) SetID(i int) {
+       typ.ID = &i
+}
+
+func (typ *TOType) Validate(db *sqlx.DB) []error {
+       errs := validation.Errors{
+               "name":         validation.Validate(typ.Name, 
validation.Required),
+               "description":  validation.Validate(typ.Description, 
validation.Required),
+               "use_in_table": validation.Validate(typ.UseInTable, 
validation.Required),
+       }
+       if errs != nil {
+               return tovalidate.ToErrors(errs)
+       }
+       return nil
+}
+
+func (typ *TOType) Read(db *sqlx.DB, parameters map[string]string, user 
auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
+       var rows *sqlx.Rows
+
+       // Query Parameters to Database Query column mappings
+       // see the fields mapped in the SQL query
+       queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+               "name":       dbhelpers.WhereColumnInfo{"typ.name", nil},
+               "id":         dbhelpers.WhereColumnInfo{"typ.id", api.IsInt},
+               "useInTable": dbhelpers.WhereColumnInfo{"typ.use_in_table", 
nil},
+       }
+       where, orderBy, queryValues, errs := 
dbhelpers.BuildWhereAndOrderBy(parameters, queryParamsToQueryCols)
+       if len(errs) > 0 {
+               return nil, errs, tc.DataConflictError
+       }
+
+       query := selectQuery() + where + orderBy
+       log.Debugln("Query is ", query)
+
+       rows, err := db.NamedQuery(query, queryValues)
+       if err != nil {
+               log.Errorf("Error querying Types: %v", err)
+               return nil, []error{tc.DBError}, tc.SystemError
+       }
+       defer rows.Close()
+
+       types := []interface{}{}
+       for rows.Next() {
+               var typ tc.TypeNullable
+               if err = rows.StructScan(&typ); err != nil {
+                       log.Errorf("error parsing Type rows: %v", err)
+                       return nil, []error{tc.DBError}, tc.SystemError
+               }
+               types = append(types, typ)
+       }
+
+       return types, []error{}, tc.NoError
+
+}
+
+func selectQuery() string {
+       query := `SELECT
+id,
+name,
+description,
+use_in_table
+FROM type typ`
+
+       return query
+}
+
+//The TOType implementation of the Updater interface
+//all implementations of Updater should use transactions and return the proper 
errorType
+//ParsePQUniqueConstraintError is used to determine if a type with conflicting 
values exists
+//if so, it will return an errorType of DataConflict and the type should be 
appended to the
+//generic error message returned
+func (typ *TOType) Update(db *sqlx.DB, user auth.CurrentUser) (error, 
tc.ApiErrorType) {
+       rollbackTransaction := true
+       tx, err := db.Beginx()
+       defer func() {
+               if tx == nil || !rollbackTransaction {
+                       return
+               }
+               err := tx.Rollback()
+               if err != nil {
+                       log.Errorln(errors.New("rolling back transaction: " + 
err.Error()))
+               }
+       }()
+
+       if err != nil {
+               log.Error.Printf("could not begin transaction: %v", err)
+               return tc.DBError, tc.SystemError
+       }
+       log.Debugf("about to run exec query: %s with type: %++v", 
updateQuery(), typ)
+       resultRows, err := tx.NamedQuery(updateQuery(), typ)
+       if err != nil {
+               if pqErr, ok := err.(*pq.Error); ok {
+                       err, eType := 
dbhelpers.ParsePQUniqueConstraintError(pqErr)
+                       if eType == tc.DataConflictError {
+                               return errors.New("a type with " + 
err.Error()), eType
+                       }
+                       return err, eType
+               }
+               log.Errorf("received error: %++v from update execution", err)
+               return tc.DBError, tc.SystemError
+       }
+       defer resultRows.Close()
+
+       var lastUpdated tc.TimeNoMod
+       rowsAffected := 0
+       for resultRows.Next() {
+               rowsAffected++
+               if err := resultRows.Scan(&lastUpdated); err != nil {
+                       log.Error.Printf("could not scan lastUpdated from 
insert: %s\n", err)
+                       return tc.DBError, tc.SystemError
+               }
+       }
+       log.Debugf("lastUpdated: %++v", lastUpdated)
+       typ.LastUpdated = &lastUpdated
+       if rowsAffected != 1 {
+               if rowsAffected < 1 {
+                       return errors.New("no type found with this id"), 
tc.DataMissingError
+               }
+               return fmt.Errorf("this update affected too many rows: %d", 
rowsAffected), tc.SystemError
+       }
+       err = tx.Commit()
+       if err != nil {
+               log.Errorln("Could not commit transaction: ", err)
+               return tc.DBError, tc.SystemError
+       }
+       rollbackTransaction = false
+       return nil, tc.NoError
+}
+
+//The TOType implementation of the Creator interface
+//all implementations of Creator should use transactions and return the proper 
errorType
+//ParsePQUniqueConstraintError is used to determine if a type with conflicting 
values exists
+//if so, it will return an errorType of DataConflict and the type should be 
appended to the
+//generic error message returned
+//The insert sql returns the id and lastUpdated values of the newly inserted 
type and have
+//to be added to the struct
+func (typ *TOType) Create(db *sqlx.DB, user auth.CurrentUser) (error, 
tc.ApiErrorType) {
+       rollbackTransaction := true
+       tx, err := db.Beginx()
+       defer func() {
+               if tx == nil || !rollbackTransaction {
+                       return
+               }
+               err := tx.Rollback()
+               if err != nil {
+                       log.Errorln(errors.New("rolling back transaction: " + 
err.Error()))
+               }
+       }()
+
+       if err != nil {
+               log.Error.Printf("could not begin transaction: %v", err)
+               return tc.DBError, tc.SystemError
+       }
+       resultRows, err := tx.NamedQuery(insertQuery(), typ)
+       if err != nil {
+               if pqErr, ok := err.(*pq.Error); ok {
+                       err, eType := 
dbhelpers.ParsePQUniqueConstraintError(pqErr)
+                       if eType == tc.DataConflictError {
+                               return errors.New("a type with " + 
err.Error()), eType
+                       }
+                       return err, eType
+               }
+               log.Errorf("received non pq error: %++v from create execution", 
err)
+               return tc.DBError, tc.SystemError
+       }
+       defer resultRows.Close()
+
+       var id int
+       var lastUpdated tc.TimeNoMod
+       rowsAffected := 0
+       for resultRows.Next() {
+               rowsAffected++
+               if err := resultRows.Scan(&id, &lastUpdated); err != nil {
+                       log.Error.Printf("could not scan id from insert: %s\n", 
err)
+                       return tc.DBError, tc.SystemError
+               }
+       }
+       if rowsAffected == 0 {
+               err = errors.New("no type was inserted, no id was returned")
+               log.Errorln(err)
+               return tc.DBError, tc.SystemError
+       }
+       if rowsAffected > 1 {
+               err = errors.New("too many ids returned from type insert")
+               log.Errorln(err)
+               return tc.DBError, tc.SystemError
+       }
+
+       typ.SetID(id)
+       typ.LastUpdated = &lastUpdated
+       err = tx.Commit()
+       if err != nil {
+               log.Errorln("Could not commit transaction: ", err)
+               return tc.DBError, tc.SystemError
+       }
+       rollbackTransaction = false
+       return nil, tc.NoError
+}
+
+//The Type implementation of the Deleter interface
+//all implementations of Deleter should use transactions and return the proper 
errorType
+func (typ *TOType) Delete(db *sqlx.DB, user auth.CurrentUser) (error, 
tc.ApiErrorType) {
+       rollbackTransaction := true
+       tx, err := db.Beginx()
+       defer func() {
+               if tx == nil || !rollbackTransaction {
+                       return
+               }
+               err := tx.Rollback()
+               if err != nil {
+                       log.Errorln(errors.New("rolling back transaction: " + 
err.Error()))
+               }
+       }()
+
+       if err != nil {
+               log.Error.Printf("could not begin transaction: %v", err)
+               return tc.DBError, tc.SystemError
+       }
+       log.Debugf("about to run exec query: %s with type: %++v", 
deleteQuery(), typ)
+       result, err := tx.NamedExec(deleteQuery(), typ)
+       if err != nil {
+               log.Errorf("received error: %++v from delete execution", err)
+               return tc.DBError, tc.SystemError
+       }
+       rowsAffected, err := result.RowsAffected()
+       if err != nil {
+               return tc.DBError, tc.SystemError
+       }
+       if rowsAffected < 1 {
+               return errors.New("no type with that id found"), 
tc.DataMissingError
+       }
+       if rowsAffected > 1 {
+               return fmt.Errorf("this create affected too many rows: %d", 
rowsAffected), tc.SystemError
+       }
+
+       err = tx.Commit()
+       if err != nil {
+               log.Errorln("Could not commit transaction: ", err)
+               return tc.DBError, tc.SystemError
+       }
+       rollbackTransaction = false
+       return nil, tc.NoError
+}
+
+func updateQuery() string {
+       query := `UPDATE
+type SET
+name=:name,
+description=:description,
+use_in_table=:use_in_table
+WHERE id=:id RETURNING last_updated`
+       return query
+}
+
+func insertQuery() string {
+       query := `INSERT INTO type (
+name,
+description,
+use_in_table) VALUES (
+:name,
+:description,
+:use_in_table) RETURNING id,last_updated`
+       return query
+}
+
+func deleteQuery() string {
+       query := `DELETE FROM type
+WHERE id=:id`
+       return query
+}
diff --git a/traffic_ops/traffic_ops_golang/types/types_test.go 
b/traffic_ops/traffic_ops_golang/types/types_test.go
new file mode 100644
index 0000000000..9cc2280248
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/types/types_test.go
@@ -0,0 +1,131 @@
+package types
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "errors"
+       "reflect"
+       "testing"
+       "time"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/test"
+       "github.com/jmoiron/sqlx"
+
+       sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1"
+)
+
+func getTestTypes() []tc.TypeNullable {
+       types := []tc.TypeNullable{}
+       ID := 1
+       name := "name1"
+       description := "desc"
+       useInTable := "use_in_table1"
+       lastUpdated := tc.TimeNoMod{Time: time.Now()}
+       testCase := tc.TypeNullable{
+               ID:          &ID,
+               LastUpdated: &lastUpdated,
+               Name:        &name,
+               Description: &description,
+               UseInTable:  &useInTable,
+       }
+       types = append(types, testCase)
+
+       testCase2 := testCase
+       name = "name2"
+       testCase2.Name = &name
+       types = append(types, testCase2)
+
+       return types
+}
+
+func TestGetType(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       testCase := getTestTypes()
+       cols := test.ColsFromStructByTag("db", tc.TypeNullable{})
+       rows := sqlmock.NewRows(cols)
+
+       for _, ts := range testCase {
+               rows = rows.AddRow(
+                       ts.ID,
+                       ts.LastUpdated,
+                       ts.Name,
+                       ts.Description,
+                       ts.UseInTable,
+               )
+       }
+       mock.ExpectQuery("SELECT").WillReturnRows(rows)
+       v := map[string]string{"dsId": "1"}
+
+       types, errs, _ := refType.Read(db, v, auth.CurrentUser{})
+       if len(errs) > 0 {
+               t.Errorf("type.Read expected: no errors, actual: %v", errs)
+       }
+
+       if len(types) != 2 {
+               t.Errorf("type.Read expected: len(types) == 2, actual: %v", 
len(types))
+       }
+
+}
+
+func TestInterfaces(t *testing.T) {
+       var i interface{}
+       i = &TOType{}
+
+       if _, ok := i.(api.Creator); !ok {
+               t.Errorf("Type must be Creator")
+       }
+       if _, ok := i.(api.Reader); !ok {
+               t.Errorf("Type must be Reader")
+       }
+       if _, ok := i.(api.Updater); !ok {
+               t.Errorf("Type must be Updater")
+       }
+       if _, ok := i.(api.Deleter); !ok {
+               t.Errorf("Type must be Deleter")
+       }
+       if _, ok := i.(api.Identifier); !ok {
+               t.Errorf("Type must be Identifier")
+       }
+}
+
+func TestValidate(t *testing.T) {
+       p := TOType{}
+       errs := test.SortErrors(p.Validate(nil))
+       expected := test.SortErrors([]error{
+               errors.New("'name' cannot be blank"),
+               errors.New("'description' cannot be blank"),
+               errors.New("'use_in_table' cannot be blank"),
+       })
+
+       if !reflect.DeepEqual(expected, errs) {
+               t.Errorf("expected %++v,  got %++v", expected, errs)
+       }
+}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to