diff --git a/client/client.go b/client/client.go index 4e03c7a..16a3679 100644 --- a/client/client.go +++ b/client/client.go @@ -28,12 +28,24 @@ func NewClient(url, username, password string) (client *Client, err error) { } func (c *Client) doRequest(method, path string, data io.Reader) (statusCode int, body string, err error) { + return c.doTypedRequest(method, path, data, "application/xml") +} + +func (c *Client) doTypedRequest(method, path string, data io.Reader, contentType string) (statusCode int, body string, err error) { + return c.doFullyTypedRequest(method, path, data, contentType, contentType) +} + +func (c *Client) doFullyTypedRequest(method, path string, data io.Reader, contentType string, acceptType string) (statusCode int, body string, err error) { request, err := http.NewRequest(method, c.URL+path, data) if err != nil { return } - request.Header.Set("Content-Type", "application/xml") - request.Header.Set("Accept", "application/xml") + if contentType != "" { + request.Header.Set("Content-Type", contentType) + } + if acceptType != "" { + request.Header.Set("Accept", acceptType) + } if c.Username != "" && c.Password != "" { request.SetBasicAuth(c.Username, c.Password) } diff --git a/client/datastore.go b/client/datastore.go index 592b4c4..e402e79 100644 --- a/client/datastore.go +++ b/client/datastore.go @@ -6,7 +6,7 @@ import ( "fmt" ) -// DatastoreRef is a reference to a Datastore +// DatastoreReference is a reference to a Datastore type DatastoreReference struct { Name string `xml:"name"` } diff --git a/client/datastore_test.go b/client/datastore_test.go index 1217b2c..130357f 100644 --- a/client/datastore_test.go +++ b/client/datastore_test.go @@ -53,18 +53,18 @@ func TestGetDatastoresSuccess(t *testing.T) { defer testServer.Close() expectedResult := []*Datastore{ - &Datastore{ + { XMLName: xml.Name{ Local: "dataStore", }, Name: "sf", Enabled: true, ConnectionParameters: []*DatastoreConnectionParameter{ - &DatastoreConnectionParameter{ + { Key: "url", Value: "file:data/sf", }, - &DatastoreConnectionParameter{ + { Key: "namespace", Value: "http://www.openplans.org/spearfish", }, @@ -123,11 +123,11 @@ func TestGetDatastoreSuccess(t *testing.T) { Name: "sf", Enabled: true, ConnectionParameters: []*DatastoreConnectionParameter{ - &DatastoreConnectionParameter{ + { Key: "url", Value: "file:data/sf", }, - &DatastoreConnectionParameter{ + { Key: "namespace", Value: "http://www.openplans.org/spearfish", }, @@ -229,11 +229,11 @@ func TestCreateDatastoreSuccess(t *testing.T) { Name: "sf", Enabled: true, ConnectionParameters: []*DatastoreConnectionParameter{ - &DatastoreConnectionParameter{ + { Key: "url", Value: "file:data/sf", }, - &DatastoreConnectionParameter{ + { Key: "namespace", Value: "http://www.openplans.org/spearfish", }, @@ -254,11 +254,11 @@ func TestCreateDatastoreSuccess(t *testing.T) { Name: "sf", Enabled: true, ConnectionParameters: []*DatastoreConnectionParameter{ - &DatastoreConnectionParameter{ + { Key: "url", Value: "file:data/sf", }, - &DatastoreConnectionParameter{ + { Key: "namespace", Value: "http://www.openplans.org/spearfish", }, @@ -285,11 +285,11 @@ func TestUpdateDatastoreSuccess(t *testing.T) { Name: "sf", Enabled: true, ConnectionParameters: []*DatastoreConnectionParameter{ - &DatastoreConnectionParameter{ + { Key: "url", Value: "file:data/sf", }, - &DatastoreConnectionParameter{ + { Key: "namespace", Value: "http://www.openplans.org/spearfish", }, @@ -310,11 +310,11 @@ func TestUpdateDatastoreSuccess(t *testing.T) { Name: "sf", Enabled: true, ConnectionParameters: []*DatastoreConnectionParameter{ - &DatastoreConnectionParameter{ + { Key: "url", Value: "file:data/sf", }, - &DatastoreConnectionParameter{ + { Key: "namespace", Value: "http://www.openplans.org/spearfish", }, diff --git a/client/featuretype.go b/client/featuretype.go index 9824e04..9a52702 100644 --- a/client/featuretype.go +++ b/client/featuretype.go @@ -17,11 +17,13 @@ type FeatureTypeKeywords struct { Keywords []string `xml:"string"` } +// FeatureTypeCRS describes CRS information type FeatureTypeCRS struct { Class string `xml:"class,attr,omitempty"` Value string `xml:",chardata"` } +// FeatureTypes is a list of FeatureType type FeatureTypes struct { XMLName xml.Name `xml:"featureTypes"` List []*FeatureType `xml:"featureType"` diff --git a/client/featuretype_test.go b/client/featuretype_test.go index f742c41..846e1a7 100644 --- a/client/featuretype_test.go +++ b/client/featuretype_test.go @@ -39,7 +39,7 @@ func TestGetFeatureTypesNoDatastoreSuccess(t *testing.T) { defer testServer.Close() expectedResult := []*FeatureType{ - &FeatureType{ + { XMLName: xml.Name{ Space: "", Local: "featureType", @@ -88,7 +88,7 @@ func TestGetFeatureTypesInDatastoreSuccess(t *testing.T) { defer testServer.Close() expectedResult := []*FeatureType{ - &FeatureType{ + { XMLName: xml.Name{ Local: "featureType", }, @@ -241,17 +241,17 @@ func TestGetFeatureTypeNoDatastoreSuccess(t *testing.T) { ProjectionPolicy: "NONE", Enabled: true, Metadata: []*FeatureTypeMetadata{ - &FeatureTypeMetadata{ + { Key: "time", Value: "false", }, - &FeatureTypeMetadata{ + { Key: "cachingEnabled", Value: "true", }, }, Attributes: []*FeatureTypeAttribute{ - &FeatureTypeAttribute{ + { Name: "the_geom", MinOccurs: 0, MaxOccurs: 1, @@ -406,17 +406,17 @@ func TestGetFeatureTypeInDatastoreSuccess(t *testing.T) { ProjectionPolicy: "NONE", Enabled: true, Metadata: []*FeatureTypeMetadata{ - &FeatureTypeMetadata{ + { Key: "time", Value: "false", }, - &FeatureTypeMetadata{ + { Key: "cachingEnabled", Value: "true", }, }, Attributes: []*FeatureTypeAttribute{ - &FeatureTypeAttribute{ + { Name: "the_geom", MinOccurs: 0, MaxOccurs: 1, @@ -554,17 +554,17 @@ func TestCreateFeatureTypeNoDatastoreSuccess(t *testing.T) { ProjectionPolicy: "NONE", Enabled: true, Metadata: []*FeatureTypeMetadata{ - &FeatureTypeMetadata{ + { Key: "time", Value: "false", }, - &FeatureTypeMetadata{ + { Key: "cachingEnabled", Value: "true", }, }, Attributes: []*FeatureTypeAttribute{ - &FeatureTypeAttribute{ + { Name: "the_geom", MinOccurs: 0, MaxOccurs: 1, @@ -628,17 +628,17 @@ func TestCreateFeatureTypeNoDatastoreSuccess(t *testing.T) { ProjectionPolicy: "NONE", Enabled: true, Metadata: []*FeatureTypeMetadata{ - &FeatureTypeMetadata{ + { Key: "time", Value: "false", }, - &FeatureTypeMetadata{ + { Key: "cachingEnabled", Value: "true", }, }, Attributes: []*FeatureTypeAttribute{ - &FeatureTypeAttribute{ + { Name: "the_geom", MinOccurs: 0, MaxOccurs: 1, @@ -705,17 +705,17 @@ func TestCreateFeatureTypeInDatastoreSuccess(t *testing.T) { ProjectionPolicy: "NONE", Enabled: true, Metadata: []*FeatureTypeMetadata{ - &FeatureTypeMetadata{ + { Key: "time", Value: "false", }, - &FeatureTypeMetadata{ + { Key: "cachingEnabled", Value: "true", }, }, Attributes: []*FeatureTypeAttribute{ - &FeatureTypeAttribute{ + { Name: "the_geom", MinOccurs: 0, MaxOccurs: 1, @@ -779,17 +779,17 @@ func TestCreateFeatureTypeInDatastoreSuccess(t *testing.T) { ProjectionPolicy: "NONE", Enabled: true, Metadata: []*FeatureTypeMetadata{ - &FeatureTypeMetadata{ + { Key: "time", Value: "false", }, - &FeatureTypeMetadata{ + { Key: "cachingEnabled", Value: "true", }, }, Attributes: []*FeatureTypeAttribute{ - &FeatureTypeAttribute{ + { Name: "the_geom", MinOccurs: 0, MaxOccurs: 1, @@ -856,17 +856,17 @@ func TestUpdateFeatureTypeNoDatastoreSuccess(t *testing.T) { ProjectionPolicy: "NONE", Enabled: true, Metadata: []*FeatureTypeMetadata{ - &FeatureTypeMetadata{ + { Key: "time", Value: "false", }, - &FeatureTypeMetadata{ + { Key: "cachingEnabled", Value: "true", }, }, Attributes: []*FeatureTypeAttribute{ - &FeatureTypeAttribute{ + { Name: "the_geom", MinOccurs: 0, MaxOccurs: 1, @@ -930,17 +930,17 @@ func TestUpdateFeatureTypeNoDatastoreSuccess(t *testing.T) { ProjectionPolicy: "NONE", Enabled: true, Metadata: []*FeatureTypeMetadata{ - &FeatureTypeMetadata{ + { Key: "time", Value: "false", }, - &FeatureTypeMetadata{ + { Key: "cachingEnabled", Value: "true", }, }, Attributes: []*FeatureTypeAttribute{ - &FeatureTypeAttribute{ + { Name: "the_geom", MinOccurs: 0, MaxOccurs: 1, diff --git a/client/style.go b/client/style.go new file mode 100644 index 0000000..e23f57a --- /dev/null +++ b/client/style.go @@ -0,0 +1,300 @@ +package client + +import ( + "bytes" + "encoding/xml" + "fmt" + "strings" +) + +// Styles is a list of Style +type Styles struct { + XMLName xml.Name `xml:"styles"` + List []*Style `xml:"style"` +} + +// Style is GeoServer Resource +type Style struct { + XMLName xml.Name `xml:"style"` + Name string `xml:"name"` + Workspace *WorkspaceRef `xml:"workspace,omitempty"` + Format string `xml:"format,omitempty"` + Version *LanguageVersion `xml:"languageVersion,omitempty"` + FileName string `xml:"filename"` +} + +// WorkspaceRef is a reference to a GeoServer workspace +type WorkspaceRef struct { + Name string `xml:"name,omitempty"` +} + +// LanguageVersion is the version of the language used to described the style +type LanguageVersion struct { + Version string `xml:"version,omitempty"` +} + +// GetHTTPContentTypeFor computes the content type of a http request for the required format and version +func (c *Client) GetHTTPContentTypeFor(format string, version string) (contentType string) { + switch format { + case "sld": + if version == "1.0.0" { + return "application/vnd.ogc.sld+xml" + } + return "application/vnd.ogc.se+xml " + case "css": + return "application/vnd.geoserver.geocss+css" + case "yaml": + return "application/vnd.geoserver.ysld+yaml" + case "json": + return "application/vnd.geoserver.mbstyle+json " + default: + return "application/vnd.ogc.sld+xml" + } +} + +// GetStyles returns all the styles +func (c *Client) GetStyles(workspace string) (styles []*Style, err error) { + var endpoint string + + if workspace == "" { + endpoint = fmt.Sprintf("/styles") + } else { + endpoint = fmt.Sprintf("/workspaces/%s/styles", workspace) + } + + statusCode, body, err := c.doRequest("GET", endpoint, nil) + if err != nil { + return + } + + switch statusCode { + case 401: + err = fmt.Errorf("unauthorized") + return + case 200: + break + default: + err = fmt.Errorf("unknown error: %d - %s", statusCode, body) + return + } + + var data Styles + + if err := xml.Unmarshal([]byte(body), &data); err != nil { + return styles, err + } + + for _, styleRef := range data.List { + style, err := c.GetStyle(workspace, styleRef.Name) + if err != nil { + return styles, err + } + + styles = append(styles, style) + } + + return +} + +// GetStyle return a single style based on its name +func (c *Client) GetStyle(workspace, name string) (style *Style, err error) { + var endpoint string + + if workspace == "" { + endpoint = fmt.Sprintf("/styles/%s", name) + } else { + endpoint = fmt.Sprintf("/workspaces/%s/styles/%s", workspace, name) + } + + statusCode, body, err := c.doRequest("GET", endpoint, nil) + if err != nil { + return + } + + switch statusCode { + case 401: + err = fmt.Errorf("unauthorized") + return + case 404: + err = fmt.Errorf("not found") + return + case 200: + break + default: + err = fmt.Errorf("unknown error: %d - %s", statusCode, body) + return + } + + var data Style + if err := xml.Unmarshal([]byte(body), &data); err != nil { + return style, err + } + + style = &data + + return +} + +// GetStyleFile retrieves the style definition of a given format +func (c *Client) GetStyleFile(workspace, name string, styleFormat string, formatVersion string) (styleFile string, err error) { + var endpoint string + + if workspace == "" { + endpoint = fmt.Sprintf("/styles/%s", name) + } else { + endpoint = fmt.Sprintf("/workspaces/%s/styles/%s", workspace, name) + } + + // Try to retrieve the style file based on the style format + contentType := c.GetHTTPContentTypeFor(styleFormat, formatVersion) + + statusCode, styleFile, err := c.doTypedRequest("GET", endpoint, nil, contentType) + if err != nil { + return + } + + switch statusCode { + case 401: + err = fmt.Errorf("unauthorized") + return + case 404: + err = fmt.Errorf("not found") + return + case 200: + break + default: + err = fmt.Errorf("unknown error: %d - %s", statusCode, styleFile) + return + } + + return styleFile, err +} + +// CreateStyle creates a style +func (c *Client) CreateStyle(workspace string, style *Style) (err error) { + var endpoint string + + if workspace == "" { + endpoint = fmt.Sprintf("/styles") + } else { + endpoint = fmt.Sprintf("/workspaces/%s/styles", workspace) + } + + style.XMLName = xml.Name{ + Local: "style", + } + payload, err := xml.Marshal(style) + if err != nil { + return + } + statusCode, body, err := c.doFullyTypedRequest("POST", endpoint, bytes.NewBuffer(payload), "application/xml", "") + + if err != nil { + return + } + + switch statusCode { + case 401: + err = fmt.Errorf("unauthorized") + return + case 201: + return + default: + err = fmt.Errorf("unknown error: %d - %s - %s", statusCode, body, string(payload)) + return + } +} + +// UpdateStyle creates a style +func (c *Client) UpdateStyle(workspace string, style *Style, styleDefinition string) (err error) { + var endpoint string + + if workspace == "" { + endpoint = fmt.Sprintf("/styles") + } else { + endpoint = fmt.Sprintf("/workspaces/%s/styles", workspace) + } + + contentType := c.GetHTTPContentTypeFor(style.Format, style.Version.Version) + + statusCode, body, err := c.doFullyTypedRequest("POST", endpoint, strings.NewReader(styleDefinition), contentType, "") + if err != nil { + return + } + + switch statusCode { + case 401: + err = fmt.Errorf("unauthorized") + return + case 201: + return + default: + err = fmt.Errorf("unknown error: %d - %s - %s", statusCode, body, styleDefinition) + return + } +} + +// UpdateStyleContent changes the style definition +func (c *Client) UpdateStyleContent(workspace string, style *Style, styleDefinition string) (err error) { + var endpoint string + + if workspace == "" { + endpoint = fmt.Sprintf("/styles/%s", style.Name) + } else { + endpoint = fmt.Sprintf("/workspaces/%s/styles/%s", workspace, style.Name) + } + + contentType := c.GetHTTPContentTypeFor(style.Format, style.Version.Version) + + statusCode, body, err := c.doFullyTypedRequest("PUT", endpoint, strings.NewReader(styleDefinition), contentType, "") + if err != nil { + return + } + + switch statusCode { + case 401: + err = fmt.Errorf("unauthorized") + return + case 200: + return + default: + err = fmt.Errorf("unknown error: %d - %s - %s", statusCode, body, styleDefinition) + return + } +} + +// DeleteStyle deletes style from GeoServer +func (c *Client) DeleteStyle(workspace string, style string, purge bool, recurse bool) (err error) { + var endpoint string + + if workspace == "" { + endpoint = fmt.Sprintf("/styles/%s?purge=%t&recurse=%t", style, purge, recurse) + } else { + endpoint = fmt.Sprintf("/workspaces/%s/styles/%s?purge=%t&recurse=%t", workspace, style, purge, recurse) + } + + statusCode, body, err := c.doRequest("DELETE", endpoint, nil) + if err != nil { + return + } + + switch statusCode { + case 401: + err = fmt.Errorf("unauthorized") + return + case 403: + err = fmt.Errorf("workspace is not empty") + return + case 404: + err = fmt.Errorf("not found") + return + case 405: + err = fmt.Errorf("forbidden") + return + case 200: + return + default: + err = fmt.Errorf("unknown error: %d - %s", statusCode, body) + return + } +} diff --git a/client/style_test.go b/client/style_test.go new file mode 100644 index 0000000..6e2dead --- /dev/null +++ b/client/style_test.go @@ -0,0 +1,554 @@ +package client + +import ( + "encoding/xml" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetStylesNoWorkspaceSuccess(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/styles", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + + w.WriteHeader(200) + w.Write([]byte(` + + + + `)) + }) + mux.HandleFunc("/styles/line", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + + w.WriteHeader(200) + w.Write([]byte(` + + `)) + }) + + testServer := httptest.NewServer(mux) + defer testServer.Close() + + expectedResult := []*Style{ + { + XMLName: xml.Name{Space: "", Local: "style"}, + Name: "line", + Workspace: &WorkspaceRef{}, + Format: "sld", + Version: &LanguageVersion{Version: "1.0.0"}, + FileName: "default_line.sld", + }, + } + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + styles, err := cli.GetStyles("") + + assert.Nil(t, err) + assert.Equal(t, expectedResult, styles) +} + +func TestGetStylesWorkspaceSuccess(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/workspaces/foo/styles", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + + w.WriteHeader(200) + w.Write([]byte(` + + + + `)) + }) + mux.HandleFunc("/workspaces/foo/styles/line", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + + w.WriteHeader(200) + w.Write([]byte(` + + `)) + }) + + testServer := httptest.NewServer(mux) + defer testServer.Close() + + expectedResult := []*Style{ + { + XMLName: xml.Name{ + Space: "", + Local: "style", + }, + Workspace: &WorkspaceRef{Name: "foo"}, + Name: "line", + Format: "sld", + Version: &LanguageVersion{Version: "1.0.0"}, + FileName: "default_line.sld", + }, + } + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + styles, err := cli.GetStyles("foo") + + assert.Nil(t, err) + assert.Equal(t, expectedResult, styles) +} + +func TestGetStyleNoWorkspaceSuccess(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/styles/line", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + + w.WriteHeader(200) + w.Write([]byte(` + + `)) + }) + + testServer := httptest.NewServer(mux) + defer testServer.Close() + + expectedResult := &Style{ + XMLName: xml.Name{ + Space: "", + Local: "style", + }, + Name: "line", + Format: "sld", + Version: &LanguageVersion{Version: "1.0.0"}, + FileName: "default_line.sld", + } + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + style, err := cli.GetStyle("", "line") + + assert.Nil(t, err) + assert.Equal(t, expectedResult, style) +} + +func TestGetStyleWorkspaceSuccess(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/workspaces/foo/styles/line", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + + w.WriteHeader(200) + w.Write([]byte(` + + `)) + }) + + testServer := httptest.NewServer(mux) + defer testServer.Close() + + expectedResult := &Style{ + XMLName: xml.Name{ + Space: "", + Local: "style", + }, + Workspace: &WorkspaceRef{Name: "foo"}, + Name: "line", + Format: "sld", + Version: &LanguageVersion{Version: "1.0.0"}, + FileName: "default_line.sld", + } + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + style, err := cli.GetStyle("foo", "line") + + assert.Nil(t, err) + assert.Equal(t, expectedResult, style) +} + +func TestGetStyleUnauthorized(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/styles/toto") + + w.WriteHeader(401) + w.Write([]byte(``)) + })) + defer testServer.Close() + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + style, err := cli.GetStyle("", "toto") + + assert.Error(t, err, "Unauthorized") + assert.Nil(t, style) +} + +func TestGetStyleNotFound(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/styles/toto") + + w.WriteHeader(404) + w.Write([]byte(``)) + })) + defer testServer.Close() + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + style, err := cli.GetStyle("", "toto") + + assert.Error(t, err, "Not Found") + assert.Nil(t, style) +} + +func TestGetStyleUnknownError(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/styles/toto") + + w.WriteHeader(418) + w.Write([]byte(`I'm a teapot!`)) + })) + defer testServer.Close() + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + style, err := cli.GetStyle("", "toto") + + assert.Error(t, err, "Unknown error: 418 - I'm a teapot!") + assert.Nil(t, style) +} + +func TestGetStyleSLDNoWorkspaceSuccess(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/styles/point", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + + w.WriteHeader(200) + w.Write([]byte(` + + + + + + Default Point + + + + Red Square point + A sample style that just prints out a red square + + + + + + + Rule 1 + Red Square point + A red fill with 6 pixels size + + + + + + square + + #FF0000 + + + 6 + + + + + + + + + + `)) + }) + + testServer := httptest.NewServer(mux) + defer testServer.Close() + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + styleFile, err := cli.GetStyleFile("", "point", "sld", "1.0.0") + + assert.Nil(t, err) + assert.NotEmpty(t, styleFile) +} + +func TestGetStyleCssNoWOrkspaceSuccess(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/styles/point", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + + w.WriteHeader(200) + w.Write([]byte(` + /* @title teal point */ + * { + mark: symbol(square); + mark-size: 6px; + :mark { + fill: #00cc33; + } + } + `)) + }) + + testServer := httptest.NewServer(mux) + defer testServer.Close() + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + styleFile, err := cli.GetStyleFile("", "point", "css", "1.0.0") + + assert.Nil(t, err) + assert.NotEmpty(t, styleFile) +} + +func TestCreateStyleNoWorkspaceSuccess(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.Path, "/styles") + + rawBody, err := ioutil.ReadAll(r.Body) + assert.Nil(t, err) + var payload *Style + err = xml.Unmarshal(rawBody, &payload) + assert.Nil(t, err) + assert.Equal(t, payload, &Style{ + XMLName: xml.Name{ + Local: "style", + }, + Name: "test_style", + FileName: "test_style.sld", + }) + + w.WriteHeader(201) + w.Write([]byte(``)) + })) + defer testServer.Close() + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + err := cli.CreateStyle("", &Style{ + XMLName: xml.Name{ + Local: "style", + }, + Name: "test_style", + FileName: "test_style.sld", + }) + + assert.Nil(t, err) +} + +func TestUpdateStyleContentSldSuccess(t *testing.T) { + const styleDefinition = ` + + + + go_test + + A teal polygon style + + + teal polygon + + + #00cc33 + + + + #000000 + 0.5 + + + + + + + + + + + ` + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "PUT") + assert.Equal(t, r.URL.Path, "/styles/toto") + assert.Equal(t, r.Header.Get("Content-Type"), "application/vnd.ogc.sld+xml") + + rawBody, err := ioutil.ReadAll(r.Body) + assert.Nil(t, err) + assert.Equal(t, string(rawBody), styleDefinition) + + w.WriteHeader(200) + w.Write([]byte(``)) + })) + defer testServer.Close() + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + styleToCreate := &Style{ + XMLName: xml.Name{ + Local: "style", + }, + Name: "toto", + FileName: "test_style.sld", + Format: "sld", + Version: &LanguageVersion{Version: "1.0.0"}, + } + + err := cli.UpdateStyleContent("", styleToCreate, styleDefinition) + + assert.Nil(t, err) +} + +func TestUpdateStyleContentCssSuccess(t *testing.T) { + const styleDefinition = ` + /* @title cyan line */ + * { + stroke: #0099cc; + } + ` + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "PUT") + assert.Equal(t, r.URL.Path, "/styles/toto") + assert.Equal(t, r.Header.Get("Content-Type"), "application/vnd.geoserver.geocss+css") + + rawBody, err := ioutil.ReadAll(r.Body) + assert.Nil(t, err) + assert.Equal(t, string(rawBody), styleDefinition) + + w.WriteHeader(200) + w.Write([]byte(``)) + })) + defer testServer.Close() + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + styleToCreate := &Style{ + XMLName: xml.Name{ + Local: "style", + }, + Name: "toto", + FileName: "test_style.css", + Format: "css", + Version: &LanguageVersion{Version: "1.0.0"}, + } + + err := cli.UpdateStyleContent("", styleToCreate, styleDefinition) + + assert.Nil(t, err) +} + +func TestDeleteStyleSuccess(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "DELETE") + assert.Equal(t, r.URL.Path, "/styles/toto") + keys, ok := r.URL.Query()["recurse"] + assert.True(t, ok) + assert.Equal(t, keys[0], "true") + keys2, ok2 := r.URL.Query()["purge"] + assert.True(t, ok2) + assert.Equal(t, keys2[0], "true") + + w.WriteHeader(200) + w.Write([]byte(``)) + })) + defer testServer.Close() + + cli := &Client{ + URL: testServer.URL, + HTTPClient: &http.Client{}, + } + + err := cli.DeleteStyle("", "toto", true, true) + + assert.Nil(t, err) +} diff --git a/client/workspaces_test.go b/client/workspaces_test.go index f5a7a54..d078135 100644 --- a/client/workspaces_test.go +++ b/client/workspaces_test.go @@ -30,10 +30,10 @@ func TestGetWorkspacesSuccess(t *testing.T) { defer testServer.Close() expectedResult := []*Workspace{ - &Workspace{ + { Name: "topp", }, - &Workspace{ + { Name: "it.geosolutions", }, } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..38925df --- /dev/null +++ b/readme.md @@ -0,0 +1,4 @@ +# Requirements + +- Install the Go package as explained on https://go.dev/doc/install +- Install the golint tool as explained on https://github.com/golang/lint